【题解】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\) 块,那这样的话就有:
然后考虑 \(l>r\) 的情况,容易发现将路径反过来统计即可。
接着可以发现,按照上式计算,\(r-l\) 其实就是中间的块数(也就是 \(a,b\)),因此可以直接省掉。然后就可以拿一个 \(dp\) 了:令 \(dp(i,j)\) 表示做完了前 \(i\) 段,现在在第 \(j\) 块的方案数。
观察这个转移式,可以发现这就是个赤裸的卷积了吧。然后就没了,但是这样做有点卡不过去。
考虑把这个 \(dp(i,j)\) 列出来:
这里 \(l\) 枚举的是中转点的位置,换个法子枚举,枚举 \(k\) 向下的步数:
然后可以发现 \({b\choose k+l-j}={b\choose b-k-l+j}\) ,这就是个范德蒙德卷积的形式了:
这后面的东西卷一下就没了。因为这样只需要考虑两侧的情况,这样子的话因为 \(|a_i-b_i|\leq 5\),所以多项式长度就是 \(O(n)\) 而非 \(O(m)\) 的。所以复杂度是 \(O(n^2\log m)\) 而不是 \(O(nm\log 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\n",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\times 10^5+6\) 了,加在一起却是 \(2\times10^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]
考虑最优的方案一定是两个连在一起就消掉,然后最后剩下的一定是一个 0、1 交错的序列,答案就为这个序列的长度除四,也就是 0 或 1 的个数除二。
怎么方便的求出这个东西?考虑原序列,一定是 0 段和 1 段交错的形式。现在考虑统计最终的 0、1 交错序列的 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\) 的方案数:
容易发现 \(-y\leq k\leq x\) ,枚举这个 \(k\) 的情况带入答案式即可,答案即为(令 \(t=a-b\)):
这个就是 \(O(n)\) 的了,足以通过 easy version 。考虑怎么修改,首先这个位置不用管吧,只需要看位置的奇偶性了。如果修改的是 0 ,其实就只需要改 \(t\) 的值即可。如果修改的是 ? ,就修改 \(x,y\) 即可。
重新定义 \(t=y+b-a\) ,令 \(p=x+y\) 。
定义 \(f(n,t)\) 。
因为每次 \(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]
只要不出现形如 bab 和 bb 的串就行,\(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}\),因此总共的区间数就是:
打表跑一跑发现 \(T=362\) 最优秀,这个看起来是 \(\sqrt{2q}\) 的样子。
接下来考虑上面那个考虑 "\(\frac{T(T-1)}{2}\) 个区间的集合" 怎么做。考虑按照值域分治,然后合并即可。计算这个需要的次数,容易发现:
容易发现后面那个东西一定小于 \(2n\),因此 \(S(n)\approx n^2\) 。
然后又跑一下,发现最小值是 \(T=256\),这个就是 \(\sqrt{q}\) 了。
Contest 1394 (Codeforces Round #664 (Div. 1)),[1800~3500]
C [*medium]
不难发现只需要使得 \(s\) 与 \(t\) 中的 B、N 出现次数分别一样即可。
可以想到将 \(s\) 化成一个点(坐标用 B、N 的出现次数来表示),画图将这些操作表示出来,不难发现形成的是一个凸的图形。考虑二分走的步数,用代数式表示这个图形,这样就变成了对于每一个 \(s\) ,图形是否有交。判断一下即可。
D [*medium]
首先如果一条边的两个端点优先级不相等,那么这条边就已经有方向了。令点 \(u\) 的入边有 \(a\) 条,出边有 \(b\) 条,那么其贡献为 \(a_u\times \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}\),因此 \(k\leq \sqrt{m}\) 时才有这个必要,显然又因为这样的点不超过 \(\frac{m}{k}\) 个,所以复杂度其实是 \(O(m\sqrt{m}\log m)\),\(\log\) 用来查边,实现优秀的话可以通过。
最后的话就是剩下的情况了。
C [*medium]
首先 \(1\) 操作不改变原序列的单调性。
接下来考虑 \(2\) 操作,容易发现一定是一段一段吃的,而且段数不超过 \(\log\) 。因为后一项不比前一项大,所以对于每一段,怎么吃都会将 \(y\) 至少吃掉一半,那么找段的话直接在线段树上二分即可。

浙公网安备 33010602011771号