【题解】2021.1.22 至 2021.1.29 简要做题记录

自然是不包括所有题 = =

仅作 (2020.1.22)(2020.1.29) 的做题记录。

注意,更新顺序是从上往下,即最新的在下面 /wq

大概都是 cf,有些没写题解的题也懒得写了,唉。


CF1473G [2800,*medium,组合数学+NTT]

首先这个绝对值的限制就保证了块数在一定限制内。

(f(l,r,x,y)) 表示当前从 (l) 个块中的第 (x) 个移动到 (r) 个块中的第 (y) 个,考虑这个 (f(l,r,x,y)) 怎么算。首先考虑 (l<r) 的情况,容易发现每一次每个块都有两种走法,也仅有两种走法。按理来说,前一列中的第 (i) 块可以走到下一列的第 (i) 块和第 (i+1) 块,那这样的话就有:

[f(l,r,x,y)={r-lchoose y-x}quad ext{if} l<r\ ]

然后考虑 (l>r​) 的情况,容易发现将路径反过来统计即可。

接着可以发现,按照上式计算,(r-l) 其实就是中间的块数(也就是 (a,b)),因此可以直接省掉。然后就可以拿一个 (dp) 了:令 (dp(i,j)) 表示做完了前 (i) 段,现在在第 (j) 块的方案数。

观察这个转移式,可以发现这就是个赤裸的卷积了吧。然后就没了,但是这样做有点卡不过去。

考虑把这个 (dp(i,j)) 列出来:

[dp(i,j)=sum_{k}dp(i-1,k)left(sum_{l}{achoose l-k}{bchoose l-j} ight) ]

这里 (l) 枚举的是中转点的位置,换个法子枚举,枚举 (k) 向下的步数:

[dp(i,j)=sum_{k}dp(i-1,k)left(sum_{l}{achoose l}{bchoose k+l-j} ight) ]

然后可以发现 ({bchoose k+l-j}={bchoose b-k-l+j}) ,这就是个范德蒙德卷积的形式了:

[dp(i,j)=sum_{k}dp(i-1,k){a+bchoose b-k+j} ]

这后面的东西卷一下就没了。因为这样只需要考虑两侧的情况,这样子的话因为 (|a_i-b_i|leq 5),所以多项式长度就是 (O(n)) 而非 (O(m)) 的。所以复杂度是 (O(n^2log m)) 而不是 (O(nmlog m)),足以通过此题。

const int N=2e3+5;
const int M=2e5+5;
const int LogN=20;

int n,a[N],b[N],fac[M],inv[M];

inline void init(int L=M-5) {
    fac[0]=1;
    lep(i,1,L) fac[i]=mul(fac[i-1],i);
    inv[L]=modpow(fac[L],-1);
    rep(i,L,1) inv[i-1]=mul(inv[i],i);
}
inline int C(int n,int m) {
    if(n<0||m<0||n<m) return 0;
    return mul(fac[n],mul(inv[m],inv[n-m]));
}

poly dp,tmp;
int main() {
    IN(n),init(),init_w();
    lep(i,1,n) IN(a[i],b[i]);
    
    int ans=0,now=1; dp.rez(2),dp[1]=1;
    lep(i,1,n) {
        int l=now,t=now+a[i],r=now+a[i]-b[i]; now=r;
        tmp.clear(),tmp.rez(l+r+1);
        lep(j,1,l+r) tmp[j]=C(a[i]+b[i],b[i]+j-l); dp=dp*tmp;
        lep(j,1,r) dp[j]=dp[j+l]; dp.rez(r+1);
    }
    lep(i,1,now) pls(ans,dp[i]); printf("%d
",ans);
    return 0;
}

CF1461F [2700,*hard,分内讨论+发掘性质+DP]

分内讨论:

  • 一个符号:

    显然那就只能全填是吧。

  • 两个符号:

    • +-:是个傻子都是直接填加号。
    • *+:额外情况。
    • *-:显然答案下限是 (0) 。对于从前往后的第一个 (0) ,显然前面都是填乘号,然后这个 (0) 前面是一个减号,后面的就都是乘号了。这样子的话后面的贡献就是 (0)。显然这是最优的情况,因为只有第一个数前面自带一个加号。
  • 三个符号:

    • *+-:显然减号完全没有用武之地,因此也属于额外情况。
  • 额外情况(有 *+):

    这个是本题的重点了。

    首先,对于所有的 (0) ,显然这个 (0) 前后都是填 + 。这样,这么多 (0) 就把原序列分成了若干段。接下来只需要考虑同一段内的。显然这并不是直接乘起来这么简单,如果有 (1) 的话,乘起来不一定是最优的。

    那么这下这么多 (1) 又把一段分成了若干区间,显然这么多区间里面都是 * ,问题就在于,每个 (1) 的前后,到底是 + 还是 *

    然后可以发现,最差的情况莫过于,中间有一堆连续的 (1) ,然后两边是否需要连起来。很显然,如果两边乘在一起大于 (lim) ,而两边加在一起在加上中间的 (1) 的和小于等于 (lim) ,则必然是将它们连起来。因为 (1) 最多 (10^5) 个,考虑最劣情况,即有一边是 (2) ,那么如果另外一边的乘积大于等于 (10^5+3) ,这样乘在一起就是 (2 imes 10^5+6) 了,加在一起却是 (2 imes10^5+5),所以这个 (lim) 其实是 (10^5) 级别的。

    那么考虑一个 (dp)(dp(i)) 表示前 (i) 个区间的最优值(显然这个 (dp(i)leq lim),因此可以直接记录),枚举上一次填加号的地方转移即可。

    然后可以发现这个 (dp) 其实是 (O(n^2)) 的,但是如果整段区间的积已经大于 (lim) 了,那么一定是全部填 * 最优,因此即使要进行 (dp) ,剩下的乘积也是小于 (lim) 的,也就是说,这个 (dp) 其实是 (O(log^2 lim)) 的。

细节挺多,代码挺长,就只贴"额外情况"的代码好了。

const int N=1e5+5;
const int lim=2e5;

int n,a[N];
char str[N];

bool work;
int dp[N],pre[N],sgn[N],val[N],L[N],R[N];
inline void build(int cnt,int l,int r) {
    L[cnt]=l,R[cnt]=r,val[cnt]=1,pre[cnt]=0,dp[cnt]=-1;
    if(!work) return ;
    lep(i,l,r) val[cnt]*=a[i]; int tmp=1;
    rep(i,cnt,1) {
        tmp*=val[i];
        if(dp[cnt]<dp[i-1]+L[i]-R[i-1]-1+tmp) dp[cnt]=dp[i-1]+L[i]-R[i-1]-1+tmp,pre[cnt]=i;
    }
}
inline void solve(int l,int r) {
    if(l>r) return ;
    int tmp=1,cnt=0; work=true;
    lep(i,l,r) if(tmp*=a[i],tmp>lim) {work=false; break;}

    std::vector <int> sta;
    lep(i,l,r) if(a[i]==1) sta.pb(i); int T=sta.size()-1;
    if(T==-1) {lep(j,l,r-1) printf("%d*",a[j]); printf("%d",a[r]); return ;}

    R[0]=l-1;
    if(l<sta[0]) build(++cnt,l,sta[0]-1);
    for(int i=0;i<T;++i) if(sta[i]+1<sta[i+1]) build(++cnt,sta[i]+1,sta[i+1]-1);
    if(r>sta[T]) build(++cnt,sta[T]+1,r);
    if(!work) pre[1]=0,pre[cnt]=1;

    lep(i,l,L[1]-1) sgn[i]=1;
    lep(i,R[cnt],r) sgn[i]=1;
    int now=cnt,las=pre[now];
    while(now) {lep(i,R[las-1],L[las]-1) sgn[i]=1; now=las-1,las=pre[now];}
    lep(i,l,r-1) printf("%d%c",a[i],sgn[i]==1?'+':'*'); printf("%d",a[r]); 
}
inline void extra() {
    std::vector <int> sta;
    lep(i,1,n) if(a[i]==0) sta.pb(i); int T=sta.size()-1;
    if(T==-1) return solve(1,n),puts(""),void();
    solve(1,sta[0]-1);
    for(int i=0;i<T;++i) {
        if(sta[i]>1) printf("+"); printf("0"); if(sta[i]<n&&sta[i]<sta[i+1]-1) printf("+");    
        solve(sta[i]+1,sta[i+1]-1);
    }
    if(sta[T]>1) printf("+"); printf("0"); if(sta[T]<n) printf("+");
    solve(sta[T]+1,n),puts("");
}

CF1458C [2700,*easy,发掘性质]

发掘一下性质,这个 IC 十分的神奇。

首先,令 ((i,j,x)) 表示 (i,j) 上的值是 (x) ,容易发现,对于 I ,其实就是将 ((i,j,x)) 变为 ((i,x,j)) ,同样对于 C,其实就是将 ((i,j,x)) 变成 ((x,j,i))

然后对于 UDLR,其实就是将 (i) 变一下或者 (j) 变一下。

可以将 IC 看作换个位置,然后将 UDLR 看作修改值,这样子的话,因为每次操作都是全体操作,所以只需要维护位置即可,然后记录一下每个位置的修改值是多少即可。这个每次修改就是 (O(1)) 的,最后重组矩阵是 (O(n^2)​) 的,足以通过此题。

const int N=1e3+5;

int n,m,mat[N][N],ans[N][N];
char str[N];

inline void solve() {
    IN(n,m);
    lep(i,0,n-1) lep(j,0,n-1) IN(mat[i][j]),--mat[i][j];
    scanf("%s",str+1); int q=strlen(str+1);

    int p1=1,p2=2,p3=3; int t[4]={0,0,0,0},o[4]={0,0,0,0}; lep(i,1,q) {
        if(str[i]=='U') --t[p1]; if(str[i]=='D') ++t[p1];
        if(str[i]=='L') --t[p2]; if(str[i]=='R') ++t[p2];
        if(str[i]=='I') swap(p2,p3); if(str[i]=='C') swap(p1,p3);
    }
    t[1]%=n,t[2]%=n,t[3]%=n;

    lep(i,0,n-1) lep(j,0,n-1) {
        o[1]=(i+t[1]+n)%n,o[2]=(j+t[2]+n)%n,o[3]=(mat[i][j]+t[3]+n)%n;
        ans[o[p1]][o[p2]]=o[p3];
    }
    lep(i,0,n-1) {lep(j,0,n-1) printf("%d ",ans[i][j]+1); puts("");}
    puts("");
}

int main() {
    int T=int(IN);
    while(T--) solve();
    return 0;
}

CF1455G [2900,*medium,建树+线段树合并优化 DP]

首先可以想到建树。

(dp(i,j)) 表示走完 (i) 的子树,出来的时候为 (j) 的最小代价。显然因为只有 if 节点有儿子,所以进入 (i) 子树的值是唯一确定的。

用线段树维护 (dp(i,j)) 。对于 (i) 的一个儿子 (u) ,如果 (u)set ,那么就只需要支持单点修改和区间修改(还要维护区间 (min)),如果 (u)if ,那么求出 (dp(u,j)) ,然后做区间加(进入 (u) 的代价)后线段树合并即可。

线段树合并的时间复杂度显然正确:一个 set 操作最多会使不同数字集合的大小加一。

然后这题就做完了。(建树好像有一堆细节要讨论,不想写了。


CF1455F [2800,*medium,字符串+发掘性质]

首先一个字符最多被操作 (1) 次,这意味着一个字符最多被移动到前面两格。

(str(i)) 表示前 (i) 个字符的最小字典序,转移的话依次考虑 (i) 的新位置为 (i,i-1,i-2) 的情况即可。


CF1468B [2900,*hard,单调性+数据结构]

发现这个结构很像一个堆栈。每一天丢进去几个,然后再取出几个栈顶。

假设第 (i) 天没有卖完,一直堆在栈中,到第 (j) 天才卖完。那么 ([i,j]) 中间的所有天的所有面包,都可以在该段天数区间内卖完。因为 (i) 对于 (k>i)(i) 在栈中的元素一定比 (k) 在栈中的元素距离栈顶远。

那么我们称这个区间 ([i,j]​) 为一个"可以独立自主解决问题的区间"。容易发现,只需要找到这些区间,然后答案就是最长的区间的长度了。接下来的问题就是怎么找这些区间。

考虑 (k)(10^9) 开始慢慢减少。最开始的时候显然区间都是 ([i,i]) 的形式。慢慢减少 (k) 的同时,会发生:

  • 一个区间的面包卖不完了,将其与下一个区间合并。

现在来考虑这个合并的时间。首先需要注意几点:

  • 如果拿一个空栈从一个区间的左端点做到右端点,除了到右端点,其他的时刻一定不存在空栈,否则区间可以被分成两半。这就意味着没有一次弹栈机会会被浪费
  • 当前一个区间有剩下的面包留到接下来一个区间时,因为剩下的面包是在栈底的,因此一定不会对这个区间的结构造成影响。

因为没有弹栈机会被浪费,所以一个区间可以接受的最小 (k) 其实就是 (frac{sum a_i}{len}) ,将其定义为区间的限制。

这下子用一个 set 维护就好了,每一次找到区间限制最大的区间,然后将其与下一个区间合并即可。

const int N=2e5+5;

ll a[N],sum[N];
int n,m,q[N],L[N],R[N];
struct Node {
    int l,r; ll val;
    bool operator < (const Node &ele) const {return val>ele.val||(val==ele.val&&r>ele.r);}
}; std::set <Node> S;

inline ll calc(int l,int r) {return (sum[r]-sum[l-1]+r-l)/(r-l+1); }
inline Node merge(Node a,Node b) {
    R[a.l]=b.r,L[b.r]=a.l;
    return (Node){a.l,b.r,calc(a.l,b.r)};
}

int ans[N],qwq[N],tim[N];
int main() {
    IN(n,m);
    lep(i,1,n) IN(a[i]); a[n]=-2e18;
    lep(i,1,n) L[i]=R[i]=i,S.insert((Node){i,i,a[i]});
    lep(i,1,n) sum[i]=sum[i-1]+a[i];

    int c=0; tim[0]=inf;
    while(S.size()>1) {
        Node now=*S.begin(),nxt=(Node){now.r+1,R[now.r+1],calc(now.r+1,R[now.r+1])};
        S.erase(now),S.erase(nxt),S.insert(merge(now,nxt));

        if(now.val<tim[c]) ++c,qwq[c]=max(qwq[c-1],nxt.r-now.l),tim[c]=now.val;
        else chkmax(qwq[c],nxt.r-now.l);
    }
    lep(i,1,c) --tim[i];
    std::reverse(tim+1,tim+1+c),std::reverse(qwq+1,qwq+1+c);
    lep(i,1,m) IN(q[i]),printf("%d ",qwq[lower_bound(tim+1,tim+1+c,q[i])-tim]);
    return puts(""),0;
}

CF1450H [2900/3300,*hard]

考虑最优的方案一定是两个连在一起就消掉,然后最后剩下的一定是一个 01 交错的序列,答案就为这个序列的长度除四,也就是 01 的个数除二。

怎么方便的求出这个东西?考虑原序列,一定是 0 段和 1 段交错的形式。现在考虑统计最终的 01 交错序列的 0 的个数。首先,对于长度为偶数的 0 段,全部都做不出贡献。其次,对于长度为奇数的 0 段,如果要做出贡献,必须满足两个奇数段不能合并到一块去,如果要满足不合并到一块去就必须满足这两段之间有奇数个元素。

也就是说,如果两个奇数段中间有偶数个元素,那么无论如何它们都会合并到一块儿去。

现在考虑对下标按照奇偶分类,容易发现,对于偶数段,下标为奇数的和下标为偶数的个数一样,对于可以合并的两个奇数段,将奇数下标和偶数下标加起来后发现他们仍然相等。对于无法合并的两个奇数段,奇数下标的个数和和偶数下标的个数和总是相差 (2),而这两个奇数段又会同时做出贡献。

因此可以发现,如果令奇数下标的 0(a) 个,偶数下标的 0(b) 个,那么答案就是 (frac{|a-b|}{2})

现在考虑有 (x) 个奇数下标的 ?,有 (y) 个偶数下标的 ?,假设从 (x) 里面找出来 (i)0(y) 里面找出来 (j)1 ,然后答案就是 (frac{|a+i-b-j|}{2}),现在令人头疼的就是这个绝对值怎么去掉。

首先求一个东西:(i-j=k) 的方案数:

[egin{aligned} f(k)&=sum_{i=0}^{x}{xchoose i}{ychoose i-k}\ &=sum_{i=0}^{x}{xchoose i}{ychoose y-i+k}\ &={y+xchoose y+k}\ end{aligned} ]

容易发现 (-yleq kleq x) ,枚举这个 (k) 的情况带入答案式即可,答案即为(令 (t=a-b)):

[ans=frac{1}{2^{x+y-1}}sum_{k=-y}^{x}[t+kequiv0mod{2}]{y+xchoose y+k}frac{|t+k|}{2} ]

这个就是 (O(n)) 的了,足以通过 easy version 。考虑怎么修改,首先这个位置不用管吧,只需要看位置的奇偶性了。如果修改的是 0 ,其实就只需要改 (t) 的值即可。如果修改的是 ? ,就修改 (x,y) 即可。

重新定义 (t=y+b-a) ,令 (p=x+y)

[egin{aligned} ans&=frac{1}{2^{p}}left(sum_{k=t+1,kequiv tmod{2}}^{p}{pchoose k}(k-t)+sum_{k=0,kequiv tmod{2}}^{t}{pchoose k}(t-k) ight)\ &=frac{1}{2^{p}}left(sum_{k=t+1,kequiv tmod{2}}^{p}{pchoose k}k-tsum_{k=t+1,kequiv tmod{2}}^{p}{pchoose k}+tsum_{k=0,kequiv tmod{2}}^{t}{pchoose k}-sum_{k=0,kequiv tmod{2}}^{ti}{pchoose k}k ight)\ &=frac{1}{2^{p}}left(sum_{k=0,kequiv tmod{2}}^{p}{pchoose k}k-tsum_{k=0,kequiv tmod{2}}^{p}{pchoose k}+2tsum_{k=0,kequiv tmod{2}}^{t}{pchoose k}-2sum_{k=0,kequiv tmod{2}}^{t}{pchoose k}k ight)\ &=frac{1}{2^{p}}left(p2^{p-2}-t2^{p-1}+2tsum_{k=0,kequiv tmod{2}}^{t}{pchoose k}-2sum_{k=0,kequiv tmod{2}}^{t}{pchoose k}k ight)\ &=frac{p-2t}{4}+frac{1}{2^{p-1}}left(tsum_{k=0,kequiv tmod{2}}^{t}{pchoose k}-psum_{k=0,k ot equiv tmod{2}}^{t-1}{p-1choose k} ight)\ &=frac{p-2t}{4}+frac{1}{2^{p-1}}left(tsum_{k=0,kequiv tmod{2}}^{t}{p-1choose k}+(t-p)sum_{k=0,k ot equiv tmod{2}}^{t-1}{p-1choose k} ight)\ end{aligned} ]

定义 (f(n,t)​)

[egin{aligned} f(n,t)&=sum_{i=0,iequiv tmod{2}}^{t}{nchoose i}\ &=sum_{i=0,iequiv tmod{2}}^{t}{n-1choose i}+sum_{i=0,iequiv (t-1)mod{2}}^{t-1}{n-1choose i}\ &=sum_{i=0}^{t}{n-1choose i}\ ans&=frac{p-2t}{4}+frac{1}{2^{p-1}}left(tf(p,t)-pf(p-1,t-1) ight)\ end{aligned} ]

因为每次 (p,t) 的变化量只有 (1),所以这个 (f(p,t)) 显然可以 (O(1)) 更新。

边界细节巨 tm 多,吐了。


CF1326F [2600/3200,*hard]

这题我感觉挺牛逼的,记录一下。F1 没多想,直接开的 F2 。

此题关键:通过容斥可以将不同的状态数从 (2^{n-1}) 变为 (P(n)),实现状态数大幅度降低。
常见的容斥也多可以用来实现降低状态数。

以下为正文部分。

观察一下一个序列代表着什么。首先,如果两个 0 连在一起,则说明中间这个点是单独的一个;如果两个 0 中间有一个 1 ,这说明中间这两个点有一条边。所以,连续的 (k)1 就代表着一个长度为 (k+1) 的链。

考虑容斥,钦定位置集合 (S) 上都是 1 ,其余位置随便的方案数为 (f(S))。这样的话将 (S) 的链集合求出来后,只需要满足链集合成立,而并不需要关系链集合之间是否成立。容易发现,位置集合 (S) 对印着原图的链大小集合 ({a_1,a_2,cdots,a_k}) ,显然有 (sum a_i=n) ,容易发现因为 (n=18),链大小集合的方案数实质上是 (n) 的划分数,因此它其实最多就是 (385) 。也就是说,最多有本质不同 (385)(f(S)) ,这下就好做多了。

然后接下来考虑如何对这 (385)(f(S)) 求解。

(2021.1.29) :暂时留坑。


Contest 1466 (Good bye 2020),[800~3400]

A [*easy]

暴力枚举另外两个端点即可。

B [*easy]

从大数到小数贪心即可。

C [*easy]

只要不出现形如 babbb 的串就行,(dp) 即可,记录前两个字符。

D [*easy]

(n-1)(1) 做,容易发现每一次会消掉一个点的贡献,每次找权值最小的可以且还可以做贡献的点即可,这个可以用数据结构随便维护。

E [*easy]

(a_j) 处统计,就是要求全局与 (a_j)(&) 和以及 (|) 和,这个只需要将位拆开统计。

F [*medium]

将每一个维度看成点,然后向量就是连边。

容易发现对于一条长度为 (n) 的链,其不同的状态数有 (2^{n-1}) 种,如果再连一条边变成环的话,状态数就有 (2^{n}) 种,但是不同的状态数仍然只有 (2^{n-1}) 。这说明环边都是没有用的。

如果从这里出发的话会发现第一个样例不好解释,因为是自环。不妨考虑建一个虚点,对于只有一个维度上有 (1) 的向量相当于将位置和虚连边。这样就解释的通了。

这个用并查集维护即可。对于 (|T|) 的话,直接用 (|S'|) 求即可。

G [*medium]

首先可以分治,将 (s_i) 的答案分成在 (s_{i-1}) 的部分和跨过 (t_{i-1}) 的部分。

(s_{i-1}) 的部分递归,然后跨过 (t_{i-1}) 的部分直接找询问串 (q) 中的相同字符,这个时候再维护一下 (s_{i-1}) 的前后缀哈希就可以算贡献了。由于 (|q|leq 10^6),因此维护前后缀哈希也就只需要维护 (10^6) 位。

然后容易发现,当 (|s_{i-1}|geq 10^6) 时,对于 (s_{i}) 的前后缀哈希,可以直接继承 (s_{i-1}) 的。这可以启发我们,也许根本不要在意是 (s_{i}) 还是 (s_{i-1}) ,反正后面的前后缀哈希都一样,需要在意的仅有中间的字符 (t) ,而不同的 (t) 只有 (26) 种,于是这题就比较可做了。

首先,处理出所有的 (|s_{i-1}|<{10^6})(s_i) 并做好前后缀哈希,因为每次字符串长度一定翻倍所以显然这样的 (i)(log) 级别的,令 (lim) 是满足条件的最大 (i)

然后询问时的 (k) ,对于 (k>lim) ,接下来的串一定是有 (2) 的若干次方的 (s_{lim}) 出现,然后开个桶记录一下这中间的 (t) 的个数,枚举 (t) 分别统计即可。

对于 (q)(s_{lim}) 中的出现次数,因为维护了前后缀哈希,所以可以很方便的求出了。(当然也可以用别的方法 ...

Contest 1375 (Codeforces Global Round 9),[1100~3500]

E [*medium]

考虑逐位还原,对于当前的第 (i) 位,找到与其形成逆序对的位置,然后显然最后要把最小的那个换到第 (i) 位,并且不破坏后面的大小关系。

显然先换大数再换小数是最优策略。对于值相同的数也需要分出个大小关系,这个最好在外面就处理好,因为在里面排序的时候再判大小关系可能会出错。

F [*hard]

发现先手必胜的状态就是,将三堆石子从小到大排序后,大小是形如 (a,a+k,a+2k) 的,并且上一次被操作过的是第三堆。假设做到了这么一个局面,但是上一次操作的并不是最大的那个,就可以给一个 (3k) ,那无论怎么操作都避免不了这种必败局面了。

事实上,我们有一个更好的方法,对于 (a,b,c) ,只需要给一个 (2c-a-b) ,这个时候只能加在 (c) 上面,否则就会直接变成必败局面。显然,加在 (c) 上后,下一次操作就不能加在 (c) 上了,这个时候就是一个妥妥的必败局面了。

G [*medium]

这题其实挺普通的。

首先很显然不会有无用操作,也就是说确立一个点为最终的菊花中心后,所有的操作一定是关于这个点的。这个时候,假设已经确定了菊花中心,将其立为根,通过画图可以很容易发现:

  • 儿子节点到该节点的边:这种边不用消掉。
  • 孙子节点到儿子节点的边:以儿子节点为 (b),这种边是需要消掉的,消掉后原孙子变成新儿子。
  • 曾孙节点到孙子节点的边:操作孙子节点的时候是会将这些点全部连到根的,因此这种边不用消掉。
  • ......

如此看来,一个边需不需要消掉,跟其到根的距离有关系。

换根 (dp) 即可。

H [*hard]

瞅了眼题解,这个值域分块还挺妙的。

考虑值域分块,每一块的大小为 (T) 。对于每个值域块,处理出 (frac{T(T-1)}{2}) 个区间的集合。接着询问的时候,将整块逐次合并即可,容易发现这里的合并次数是 (frac{n}{T}),因此总共的区间数就是:

[frac{n(T-1)}{2}+frac{qn}{T} ]

打表跑一跑发现 (T=362) 最优秀,这个看起来是 (sqrt{2q}) 的样子。

接下来考虑上面那个考虑 "(frac{T(T-1)}{2}) 个区间的集合" 怎么做。考虑按照值域分治,然后合并即可。计算这个需要的次数,容易发现:

[S(n)=frac{n}{2}sum_{i=0}^{2^ileq n}2^i-1 ]

容易发现后面那个东西一定小于 (2n),因此 (S(n)approx n^2)
然后又跑一下,发现最小值是 (T=256),这个就是 (sqrt{q}) 了。

Contest 1394 (Codeforces Round #664 (Div. 1)),[1800~3500]

C [*medium]

不难发现只需要使得 (s)(t) 中的 BN 出现次数分别一样即可。

可以想到将 (s) 化成一个点(坐标用 BN 的出现次数来表示),画图将这些操作表示出来,不难发现形成的是一个凸的图形。考虑二分走的步数,用代数式表示这个图形,这样就变成了对于每一个 (s) ,图形是否有交。判断一下即可。

D [*medium]

首先如果一条边的两个端点优先级不相等,那么这条边就已经有方向了。令点 (u) 的入边有 (a) 条,出边有 (b) 条,那么其贡献为 (a_u imes max(a,b))

对于没有方向的边,考虑 (dp) 。令 (dp_{u,0/1}) 表示对于 (u) 来说,父亲到其的边是入边/出边时,子树内的最小代价。转移的时候对于不能确定方向的边所连儿子全部都拿出来,然后钦定这些儿子全部选的入边,最后再逐次换成出边计算贡献即可,要求最优方案就将儿子按照 (dp) 状态的差排序即可。

Contest 1439 (Codeforces Round #684 (Div. 1)),[1500~3500]

B [*medium]

先将无用的度数小于 (k-1) 的点全部删去,这些点必然是没有贡献的。

然后就是删度数等于 (k-1) 的点,删的时候判断一下能不能团,显然团的边数是 (frac{k(k-1)}{2}),因此 (kleq sqrt{m}) 时才有这个必要,显然又因为这样的点不超过 (frac{m}{k}) 个,所以复杂度其实是 (O(msqrt{m}log m))(log) 用来查边,实现优秀的话可以通过。

最后的话就是剩下的情况了。

C [*medium]

首先 (1) 操作不改变原序列的单调性。

接下来考虑 (2) 操作,容易发现一定是一段一段吃的,而且段数不超过 (log) 。因为后一项不比前一项大,所以对于每一段,怎么吃都会将 (y) 至少吃掉一半,那么找段的话直接在线段树上二分即可。

原文地址:https://www.cnblogs.com/losermoonlights/p/14347653.html