板刷CF记录 #2459878

太简单的 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(

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值