UOJ 143

首先任意长度为二的子序列都是等差子序列,所以我们想让长度 $\geq 3$ 的子序列都不是等差子序列:

我们可以利用奇偶性:每次奇数放左边,偶数放右边,两边除二递归下去,保证了每次跨越中心的所有长度 $\geq 3$ 的子序列差都奇偶性不同。

构造不同的东西的时候可以考虑奇偶性。

一个智力题

给一个最大团大小不小于 $\frac{2n}{3}$ 的图,求一个大小为 $\frac{n}{3}$ 的团。

每次选择一对没有连边的点删除,这样最后会留下至少 $\frac{n}{3}$ 个点,并且都在团中。

一个经典题

数列 $a_i$,每次操作 $(a_{i-1},a_i,a_{i+1}) \to (a_{i-1}-a_i,-a_i,a_{i+1}-a_i)$ 问两个数列能否操作可达。

判断序列相同要么是找出操作可逆,要么是转化为前缀和差分来看。

这里我们前缀和,等价于交换两个数字,所以判断值域是否相同即可。

一个题

$$ f_i = \min\{f_j+\lfloor \frac{i+j-1}{j}\rfloor p+v,ip+v \} $$

求 $f_{10^{15}}$。

考虑值域很小,并且 $f$ 单调不降,翻转值域和定义域:设 $g_i$ 表示值 $i$ 最右能到哪里,每个位置每次只会用每种 $f$ 值最左边的位置转移,设值域为 $V$,复杂度 $O(V^2)$。

超现实树

当时还是菜啊。。

定义一个树为好树,当且仅当这个树每个点左儿子或者右儿子的子树大小为 $1$。不难发现所有树都能由同深度的好树生成。那么不能生成的树是有限的等价于不能生成的好树是有限的。

现在我们要判断这个森林不能生成的好树是不是有限的,如果存在一个点的树,那么一定是几乎完备的,否则每次可以先把左右儿子子树大小都 $\geq 2$ 的扔了(不可能生成好树),然后按照以下分类:

  • 只有左儿子
  • 只有右儿子
  • 左儿子大小为 $1$,有右儿子
  • 右儿子大小为 $1$,有左儿子

发现当前森林是几乎完备的充要条件是这四种森林都是完备的(因为这对应了四种不同的好树形态),递归下去做就行了。

一个套路

平面 $n$ 个点,求一般图最大权匹配,两点之间边权为切比雪夫距离。保证 $n$ 是偶数。

首先切比雪夫转曼哈顿距离:

曼哈顿距离转切比雪夫距离:$(x,y) \to (x+y,x-y)$

切比雪夫距离转曼哈顿距离:$(x,y) \to (\frac{x+y}{2},\frac{x-y}{2})$

答案的上界是每一维独立后的最大答案的和。

构造我们可以找到 $x_i$ 的中位数 $X$,$y_i$ 的中位数 $Y$,在 $(X,Y)$ 建立坐标系,发现左上方和右下方可以一一匹配;右上方和左下方可以一一匹配,并且都达到了答案的上界,并且还能发现左上和右下的点数是相同的,右上和左下的点数是相同的,证明了匹配的可行性。

ARC084 F

暴力:将 $O(nm)$ 个数插入线性基,查询的时候先将线性基消成主元列上只有一个 $1$ 的形式,然后从高到低考虑每个主元列,如果异或上去后还是 $\leq$ 就说明这一位如果选 $0$ 的话后面就可以随便选,答案加上 $2^{cnt-i}$,否则说明后面有限制,就继续往后看。这样复杂度是 $O(\frac{n^2m}{w})$ 的,过不了。

正解考虑将每个数看做一个系数模 $2$ 的多项式,我们首先用模 $2$ 加法定义多项式加法,然后左移定义乘一个单项式 $z$,那么我们就有了多项式乘法。不难发现 $(x,y)$ 和 $(x,y-x \times z^k)$ 能表示出的数是一样的(因为这里的减法也是加法(异或有自反律),并且可以自由组合无限次),所以我们可以用类似于辗转相减的方法直到一个变成 $0$,也就是变成了 $N=1$ 的问题。

举一个这里乘法的例子:

$$ \begin{aligned} (x^0+x^1+x^2) \times (x^0+x^2) &= (x^0+x^1+x^2) + (x^2+x^3+x^4)\\ &= x^0+x^1+x^3+x^4 \end{aligned} $$

在二进制下的表现就是:

$$ (111)_2 \times (101)_2 = (11011)_2 $$

$N=1$ 的问题实际上就是问 $x$ 的倍数个数,这里 $P$ 是 $Q$ 的倍数的定义是存在一个多项式 $C$,满足 $P=QC$。倍数也可以是理解为多个 $Q$ 按照不同的方式左移后加起来。

我们先考虑如何去判定 $P$ 是否是 $Q$ 的倍数:找到 $P$ 的最高位,消掉,然后递归继续。不能消的时候判断一下后面是否都是 $0$ 就好了。

那么我们设 $\gcd$ 为 $G$,位数为 $g$;输入的 $X$ 位数为 $x$,我们发现从高到低的前 $g-x+1$ 可以随便去,并且这样就固定了后面的位。所以答案就是前 $g-x+1$ 位的数。

但是注意到我们如果前 $g-x+1$ 都卡上界,后面唯一确定的数可能打破上界,这种模拟+特判就好了。

复杂度是 $O(\frac{nm^2}{w})$ ,其实如果考察手写 bitset 就可以开大点(bushi

ARC066 D

本质是要求出强制选一个点的答案。

这种经典问题我们考虑分治:设我们现在正在处理区间 $[l,r]$,取 $mid$,我们对于 $[l,mid]$ 加上右边的贡献,对于 $[mid+1,r]$ 加上左边的贡献。首先处理出 $f_i,g_i$ 表示 $i$ 前缀最后一次选 $i$ 的答案, $g_i$ 表示 $i$ 后缀最后一次选 $i$ 的答案(可以斜率优化 $O(n)$ 求出),然后在分治内部转移的时候改成枚举上一个不选的位置从另一个区间转移过来即可。

复杂度 $O(n \log n)$

这里转换单点修改等价于强制选这个点还是很妙的。

#include<bits/stdc++.h>

#define fi first
#define se second
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 5e5 + 5;
int n,a[MAXN];

int st[MAXN],tp;
LL pre[MAXN],suf[MAXN],sm[MAXN],t[MAXN];
#define X(i) (i)
#define Y(i) (t[i]+sm[i]+(1ll*i*i-i)/2.0)

inline double slope(int x,int y){ // x-y
    return (Y(x)-Y(y))/(X(x)-X(y));
}

inline void dp(){
    FOR(i,1,n) sm[i] = sm[i-1]+a[i];st[tp = 1] = 0;
    FOR(i,1,n){
        while(tp > 1 && slope(st[tp-1],st[tp]) <= i) tp--;
        int j = st[tp];
        // if(i == 2) DEBUG(j),DEBUG(t[j]+(i-j)*(i-j+1)/2-sm[i]+sm[j]);
        t[i] = std::max(t[i-1],t[j]+1ll*(i-j)*(i-j+1)/2-sm[i]+sm[j]);
        while(tp > 1 && slope(st[tp-1],st[tp]) < slope(st[tp],i)) tp--;
        st[++tp] = i;
    }
}

LL f[MAXN],ans[MAXN],tmp[MAXN];

inline void cdq(int l,int r){
    if(l == r){
        f[l] = std::max(f[l],pre[l-1]+suf[r+1]+1-a[l]);
        return;
    }
    int mid = (l + r) >> 1;
    st[tp = 1] = l-1;
    FOR(i,l,mid-1){
        while(tp > 1 && slope(st[tp-1],st[tp]) < slope(st[tp],i)) tp--;
        st[++tp] = i;
    }
    FOR(i,mid,r){
        while(tp > 1 && slope(st[tp-1],st[tp]) <= i) tp--;
        int j = st[tp];
        tmp[i] = t[j]+1ll*(i-j)*(i-j+1)/2-sm[i]+sm[j]+suf[i+1];
    }
    ROF(i,r-1,mid) tmp[i] = std::max(tmp[i+1],tmp[i]);
    FOR(i,mid,r) f[i] = std::max(f[i],tmp[i]);
    cdq(l,mid);cdq(mid+1,r);
}

int main(){
    scanf("%d",&n);
    FOR(i,1,n) scanf("%d",a+i);
    dp();FOR(i,1,n) pre[i] = t[i],t[i] = 0;
    std::reverse(a+1,a+n+1);dp();std::reverse(t+1,t+n+1);std::reverse(a+1,a+n+1);
    FOR(i,1,n) suf[i] = t[i],t[i] = 0,ans[i] = -1e18;

    FOR(i,1,n) t[i] = pre[i],sm[i] = sm[i-1]+a[i],f[i] = -1e18;cdq(1,n);
    FOR(i,1,n) ans[i] = std::max(ans[i],f[i]);

    std::reverse(a+1,a+n+1);std::reverse(pre+1,pre+n+1);std::reverse(suf+1,suf+n+1);
    std::swap(pre,suf);
    FOR(i,1,n) t[i] = pre[i],sm[i] = sm[i-1]+a[i],f[i] = -1e18;cdq(1,n);
    std::reverse(f+1,f+n+1);
    FOR(i,1,n) ans[i] = std::max(ans[i],f[i]);
    std::reverse(a+1,a+n+1);std::reverse(pre+1,pre+n+1);std::reverse(suf+1,suf+n+1);
    std::swap(pre,suf);

    // FOR(i,1,n) DEBUG(ans[i]);

    int q;scanf("%d",&q);
    while(q--){
        int x,y;scanf("%d%d",&x,&y);
        printf("%lld\n",std::max(pre[x-1]+suf[x+1],ans[x]+a[x]-y));
    }
    return 0;
}

代码写的这么快的原因必然是之前做过

Last modification:October 8th, 2020 at 04:32 pm
如果觉得我的文章对你有用,请随意赞赏