太简单的 A 和 B 就不放了。
考虑到有些人(比如我)或者有些时候(比如我在家时)不一定上的去 Codeforces,这里放的是洛谷的链接。
CF165C
题意:给你一个 01 串,问有多少刚好含 k 个 1 的子串。
直接 dp 就好了。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000000+5;
typedef long long ll;
char s[maxn];
int f[maxn];
ll ans;
int main(){
int k;cin>>k;
scanf("%s",s+1);
int n=strlen(s+1),cnt=0;
f[0]=1;
for(int i=1;i<=n;i++){
cnt+=s[i]=='1';
if(cnt>=k)ans+=f[cnt-k];
f[cnt]++;
}
cout<<ans;
}
CF165D
题意:维护一棵初始全黑的树,支持修改边的颜色为白 / 黑,每次询问两点之间黑边数量,若两点间有白边则输出 -1。
直接树链剖分。记得边转点三部曲:
- 保存每条边。两边 dfs 之后把边上深度较大的那个点设作这条边的代表点;
- 处理询问时,最后一个询问要把深度较浅的那个点变成重儿子(如果不这样做会多算一条边)
- 检查数组大小。
如果你能在 10 分钟内码出来,那你的树链剖分就很熟练了。我码了 20+ 分钟(因为在调)。
#include<bits/stdc++.h>
using namespace std;
const int maxn=100000+5;
namespace segtree{
int val[maxn*4],wh[maxn*4];
inline void pushup(int root){
val[root]=val[root*2]+val[root*2+1];
wh[root]=wh[root*2]||wh[root*2+1];
}
void build(int root,int l,int r){
if(l==r){
val[root]=1,wh[root]=0;
return;
}
int mid=(l+r)>>1;
build(root*2,l,mid),build(root*2+1,mid+1,r);
pushup(root);
}
void edit(int qx,int root,int l,int r,int v){
if(l==r){
val[root]=v,wh[root]=v?0:1;
return;
}
int mid=(l+r)>>1;
if(qx<=mid)edit(qx,root*2,l,mid,v);
else edit(qx,root*2+1,mid+1,r,v);
pushup(root);
}
int qry(int ql,int qr,int root,int l,int r){
if(ql<=l&&r<=qr)return val[root];
int ans=0,mid=(l+r)>>1;
if(ql<=mid)ans+=qry(ql,qr,root*2,l,mid);
if(qr>mid)ans+=qry(ql,qr,root*2+1,mid+1,r);
return ans;
}
int qrywhite(int ql,int qr,int root,int l,int r){
if(ql<=l&&r<=qr)return wh[root];
int ans=0,mid=(l+r)>>1;
if(ql<=mid)ans=ans||qrywhite(ql,qr,root*2,l,mid);
if(qr>mid)ans=ans||qrywhite(ql,qr,root*2+1,mid+1,r);
return ans;
}
}
using namespace segtree;
vector<int> nxt[maxn];
int siz[maxn],d[maxn],wson[maxn],fa[maxn];
int id[maxn],rev[maxn],top[maxn],clc;
void dfs1(int x,int f){
siz[x]=1,d[x]=d[f]+1,fa[x]=f;
for(auto a:nxt[x])if(a!=f){
dfs1(a,x);
if(siz[a]>siz[wson[x]])wson[x]=a;
siz[x]+=siz[a];
}
}
void dfs2(int x,int chaintop){
id[x]=++clc,rev[clc]=x,top[x]=chaintop;
if(wson[x])dfs2(wson[x],chaintop);
for(auto a:nxt[x])if(a!=fa[x]&&a!=wson[x])dfs2(a,a);
}
int n,q,u[maxn],v[maxn],rep[maxn];
inline void mk(){
for(int i=1;i<n;i++)rep[i]=d[u[i]]>d[v[i]]?u[i]:v[i];
}
inline void mdf_point(int i,int col){
edit(id[rep[i]],1,1,n,col);
}
inline int chain_sum(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(d[top[x]]<d[top[y]])swap(x,y);
ans+=qry(id[top[x]],id[x],1,1,n);
x=fa[top[x]];
}
if(x==y)return ans;
if(id[x]>id[y])swap(x,y);
return ans+qry(id[wson[x]],id[y],1,1,n);
}
inline int chain_white(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(d[top[x]]<d[top[y]])swap(x,y);
ans=ans||qrywhite(id[top[x]],id[x],1,1,n);
x=fa[top[x]];
}
if(x==y)return ans;
if(id[x]>id[y])swap(x,y);
return ans||qrywhite(id[wson[x]],id[y],1,1,n);
}
int main(){
cin>>n;
for(int i=1;i<n;i++){
cin>>u[i]>>v[i];
nxt[u[i]].push_back(v[i]);
nxt[v[i]].push_back(u[i]);
}
dfs1(1,0),dfs2(1,1),mk(),build(1,1,n);
cin>>q;
while(q--){
int op,u,v;cin>>op>>u;
if(op==3){
cin>>v;
if(chain_white(u,v))puts("-1");
else cout<<chain_sum(u,v)<<endl;
}
else mdf_point(u,2-op);
}
}
CF165E
题意:给你一个序列 a,对于每个 a[i],找一个 a[j] 使得 a[i]&a[j]=0。
考虑这样一个事实:假设 a&b=0,那么如果把 a 的二进制表示下的随便几个 1 换成 0,那 a&b=0 仍然成立。
也就是说,对于每一个 a[i],我们可以造一个含尽量多位 1 的数 v 使得 a[i]&v=0。这些 v 将会构成一个集合 S。算 v 的方式很简单:((1<<22)-1)&a[i] 就行了。开一个 map 保存,初始让 ans[((1<<22)-1)&a[i]]=a[i]。其他的 x 让 ans[x]=-1。
造完之后,for 每一个 a[i],检查 a[i] 能不能通过增添若干位的 1 变成 S 中的某一个数。如果可以,那么就说明 a[i] 能够找到一个数了。
增添若干位的 1?枚举增添多少位、然后枚举在哪些位增添吗?C(22,0)+C(22,1)+C(22,2)+…+C(22,22)=2^22,然后时间复杂度是 O(2^22*n),还不如暴力 n^2,T 飞。
答案是需要用 dp。设 f[x] 表示数 x 的答案,那么检查 f[x] 能不能增添一位 1 变成 S 中的数。如果可以,那么把 x 也加入集合,让 ans[x]=ans[增添一位之后的数]。这个正确性显然。
然后你会惊奇地发现,这样就可以 O(22 * 2^22) 不重不漏地计算了。
早上我在写的时候还是紫题呢,怎么变蓝了
好像有高位前缀和做法?FWT?不会,以后学(
#include<bits/stdc++.h>
using namespace std;
const int inf=4194304;
int a[inf+5],f[inf+5],n;
int main(){
cin>>n;for(int i=1;i<=n;i++)cin>>a[i];
memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++)f[~a[i]&(inf-1)]=a[i];
for(int i=inf-1;i>0;i--)if(f[i]==-1){
for(int j=0;j<22;j++)if(!((i>>j)&1)){
if(f[i|(1<<j)]!=-1){
f[i]=f[i|(1<<j)];
break;
}
}
}
for(int i=1;i<=n;i++)cout<<f[a[i]]<<' ';
}
Conclusion
从 E 题学了个小套路:对于需要“增添若干位”的题,可以考虑搞一个大小和值域相当的 dp 来做。
以及是真的累,想回归自然了所以不用 latex(