22.8.23 总结
CF1720D1
由于该题涉及到了子序列,我们先一眼看出这一题是一道动态规划。
根据以往的经验,于是我们先列出状态 \(f_i\) 表示以 \(i\) 结尾的 \(b\) 序列最长长度。
\(O(n^2)\) 暴力方程为: \(f_i=\max_{0\le j <i}(f_j+1),(a_j⊕i<a_i⊕j)\).
瓶颈为寻找最大的 \(f_j\) 需在 \(0\le j <i\) 中寻找。
在这一题中 \(a_i\le 200\) ,那么我们思考:
当\(i>j+256\) 时,不考虑二进制后面 \(8\) 位,\(i>j\).
由于 \(a_i\le200\),所以 \(a_i\) 只能影响后 \(8\) 位。
所以 \(a_j⊕i<a_i⊕j\) 一定对 \(i>j+256\) 不成立。
所以新的方程是: \(f_i=\max_{i-256+1\le j <i}(f_j+1),(a_j⊕i<a_i⊕j)\).
复杂度 \(O(256n)\).
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=3e5+10,M=256;
int t,n,a[N],f[N],ans;
int main() {
scanf("%d",&t);
for(; t; t--) {
scanf("%d",&n);
ans=0;
for(int i=0; i<n; i++) scanf("%d",&a[i]);
for(int i=0; i<n; i++) f[i]=1;
for(int i=0; i<n; i++)
for(int j=max(0,i-M+1); j<i; j++)
if((a[j]^i)<(a[i]^j))
f[i]=max(f[i],f[j]+1);
for(int i=0; i<n; i++) ans=max(ans,f[i]);
printf("%d\n",ans);
}
return 0;
}
CF1716D
显然动态规划。设 \(f_{i,j}\) 为走了 \(i\) 步,走到 \(j\) 的方案数,令 \(d=i+k-1\)。
原始方程为 \(f_{i,j}=\sum_{k>0} f_{i-1,j-kd}\)
用前缀和优化一下,变成了 \(f_{i,j}=sum_j\).
\(sum_j=sum_{j-d}+f_{i-1,j}\).
由于最多走 \(\sqrt{n}\) 步,时间复杂度 \(O(n\sqrt n)\),空间复杂度用滚动数组降到 \(O(n)\).
#include<cstdio>
using namespace std;
const int N=5e5+10,mod=998244353;
int n,k,m,t,f[2][N],sum[N],ans[N];
int main() {
scanf("%d%d",&n,&k);
while(t<=n) {m++; t+=(m+k-1);}
f[0][0]=1;
for(int i=1; i<=m; i++) {
int d=i+k-1;
for(int j=0; j<=n; j++) sum[j]=((j-d>=0?sum[j-d]:0)+f[i&1^1][j])%mod;
for(int j=0; j<=n; j++) f[i&1][j]=(j-d>=0?sum[j-d]:0)%mod;
for(int j=0; j<=n; j++) ans[j]=(ans[j]+f[i&1][j])%mod;
}
for(int i=1; i<=n; i++) printf("%d ",ans[i]%mod);
return 0;
}
CF1715D
一道构造。
对于这种二进制操作的题目,我们应该拆位处理。
我们先把所有限制条件建成图。
对于一条边,如果某一位为0,那么其两个端点都在必须这位为0。
填完所有的0之后,考虑字典序最小,尝试把一些1变成0。
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int N=2e5+10,M=30,inf=(1<<30)-1;
int n,q,tot,head[N],ver[2*N],nxt[2*N],edge[2*N];
int p[N],vis[N];
void addedge(int x,int y,int z) {
ver[++tot]=y;
edge[tot]=z;
nxt[tot]=head[x];
head[x]=tot;
}
int main() {
scanf("%d%d",&n,&q);
for(int i=1; i<=n; i++) p[i]=inf;
for(int i=1,x,y,z; i<=q; i++) {
scanf("%d%d%d",&x,&y,&z);
addedge(x,y,z); addedge(y,x,z);
vis[x]=vis[y]=1;
for(int j=M-1; j>=0; j--) {
if(!(z&(1<<j))) {
p[x]&=(inf^(1<<j));
p[y]&=(inf^(1<<j));
}
}
}
for(int x=1; x<=n; x++) {
for(int j=M-1; j>=0; j--) {
bool flag=1;
if(p[x]==(p[x]&(inf^(1<<j)))) continue;
p[x]&=inf^(1<<j);
for(int i=head[x]; i; i=nxt[i]) {
int y=ver[i],z=edge[i];
if((p[x]|p[y])!=z) flag=0;
}
if(!flag) p[x]|=(1<<j);
}
}
for(int i=1; i<=n; i++) {
if(!vis[i]) {printf("0 "); continue;}
printf("%d ",p[i]);
}
return 0;
}
CF1718A2
DP好题。
我们设 \(f_i\) 为前 \(i\) 个数都变成 \(0\) 需要的秒数,那么如何转移呢?
首先我们知道 \(f_i\) 可以由 \(f_{i-1}+1\) 转移而来.
那么对 \(f_{i-2}\) 呢? 若 \(a_i⊕a_{i-1}=0(a_i=a_{i-1})\) 那么 \(f_i=f_{i-2}+1\).
那么对 \(f_{i-3}\) 呢? 若 \(a_i⊕a_{i-1}⊕a_{i-2}=0\) 那么 \(f_i=f_{i-3}+2\).
这是因为: \(a_{i-2}⊕a_{i-2}=0,a_{i-1}⊕a_{i-2}=a_i,a_i⊕a_i=0\).
那么对 \(f_j\) 呢? 若 \(a_j⊕a_{j+1}⊕...⊕a_i=0\) 那么 \(f_i=f_j+i-j\).
用前缀异或和。
#include<algorithm>
#include<cstdio>
#include<iostream>
#include<map>
using namespace std;
const int N=100010;
int n,a[N],b[N],t,f[N];
map<int,int> mp;
int main() {
scanf("%d",&t);
for(; t; t--) {
scanf("%d",&n); mp.clear(); mp[0]=0;
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
for(int i=1; i<=n; i++) b[i]=b[i-1]^a[i];
for(int i=1; i<=n; i++) {
f[i]=f[i-1]+(a[i]>0);
if(mp.count(b[i])) {
int j=mp[b[i]];
f[i]=min(f[i],f[j]+i-(j+1));
}
mp[b[i]]=i;
}
printf("%d\n",f[n]);
}
return 0;
}