codechef november challenge 2020 division 2

题解赛后发。
最后一题没看懂题意就没做
结果发现我第一次理解的题意是对的。。。。。。。
感觉这次题目没有什么思维难度。
ADADISH
签到。

#include<bits/stdc++.h>
using namespace std;
#define N 5010
int n,T,a[N],f[N];
int main(){
	scanf("%d",&T);
	while(T--){
		int s=0;
		memset(f,0,sizeof(f));
		scanf("%d",&n);
		for(int i=1;i<=n;i++){
			scanf("%d",&a[i]);
			s+=a[i];
		}
		f[0]=1;
		for(int i=1;i<=n;i++)
			for(int j=s;j>=a[i];j--)
				f[j]|=f[j-a[i]];
		int ans=1e9;
		for(int i=1;i<=s;i++)
			if(f[i])
				ans=min(ans,max(s-i,i));
		printf("%d\n",ans);
	}
}

RESTORE
签到。

#include<bits/stdc++.h>
using namespace std;
#define N 2000010
int T,n,b[N],ct,p[N],vi[N],cv;
int main(){
	scanf("%d",&T);
	for(int i=2;i<N;i++){
		if(!vi[i])
			p[++ct]=i;
		for(int j=1;j<=ct&&p[j]*i<N;j++){
			vi[i*p[j]]=1;
			if(i%p[j]==0)
				break;
		}
	}
	while(T--){
		cv=0;
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%d",&b[i]);
		for(int i=1;i<=n;i++)
			vi[i]=0;
		for(int i=1;i<=n;i++){
			if(!vi[b[i]])
				vi[b[i]]=++cv;
			printf("%d ",p[vi[b[i]]]);
		}
		puts("");
	}
}


FEMA2
把序列从堵住的格子劈开,使用两个set维护磁铁和铁块,贪心的选择距离最远的进行匹配。

#include<bits/stdc++.h>
using namespace std;
#define N 500010
int T,n,k,p[N],ans;
char s[N];
multiset<int>s1,s2;
void gt(int x,int y){
	p[x-1]=0;
	for(int i=x;i<=y;i++){
		p[i]=p[i-1];
		if(s[i]==':')
			p[i]++;
	}
	s1.clear();
	s2.clear();
	for(int i=x;i<=y;i++){
		if(s[i]=='I'){
			if(s1.empty()){
				s2.insert(i+p[i]);
				continue;
			}
			multiset<int>::iterator it=s1.upper_bound(i+p[i]-k-1);
			if(it!=s1.end()){
				s1.erase(it);
				ans++;
			}
			else
				s2.insert(i+p[i]);
		}
		else if(s[i]=='M'){
			if(s2.empty()){
				s1.insert(i+p[i]);
				continue;
			}
			multiset<int>::iterator it=s2.upper_bound(i+p[i]-k-1);
			if(it!=s2.end()){
				s2.erase(it);
				ans++;
			}
			else
				s1.insert(i+p[i]);
		}
	}
}
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%s",&n,&k,s+1);
		ans=0;
		for(int i=1;i<=n;i++)
			if(s[i]!='X'){
				int j=i;
				while(j<n&&s[j+1]!='X')
					j++;
				gt(i,j);
				i=j;
			}
		for(int i=1;i<=n;i++)
			p[i]=0;
		printf("%d\n",ans);
	}
}

CNDYGAME
按照1的位置分类讨论。
这道简单题搞了我很久。。。。。

#include<bits/stdc++.h>
using namespace std;
#define N 300010
#define int long long
#define mo 1000000007
int T,a[N],s[N],b[N];
signed main(){
	scanf("%lld",&T);
	while(T--){
		int n,q;
		scanf("%lld",&n);
		int po=0;
		for(int i=1;i<=n;i++)
			scanf("%lld",&a[i]);
		for(int i=1;i<=n;i++)
			if(a[i]==1){
				po=i;
				break;
			}
		if(po==1){
			scanf("%lld",&q);
			while(q--){
				int x,ans;
				scanf("%lld",&x);
				if(n!=1){
					ans=x/n;
					if(x%n!=0)
						ans++;
					if(x%n==1&&x!=1)
						ans--;
					printf("%lld\n",ans%mo);
				}
				else
					printf("%lld\n",x%mo);
			}
		}
		else if(po==n||!po){
			for(int i=1;i<=n;i++){
				if(i!=n){
					if(a[i]%2==0){
						s[i]=(s[i-1]+a[i])%mo;
						b[i]=a[i];
					}
					else{
						s[i]=(s[i-1]+a[i]-1)%mo;
						b[i]=a[i]-1;
					}	
				}	
				else{
					if(a[i]%2==1){
						s[i]=(s[i-1]+a[i])%mo;
						b[i]=a[i];
					}
					else{
						s[i]=(s[i-1]+a[i]-1)%mo;
						b[i]=a[i]-1;
					}
				}
			}
			scanf("%lld",&q);
			while(q--){
				int x,ans;
				scanf("%lld",&x);
				ans=s[n]*((x/n)%mo)%mo;
				x%=n;
				if(!x){
				    if(a[n]!=b[n])
						printf("%lld\n",(ans+1)%mo);
					else
						printf("%lld\n",ans);
				}
				else{
					ans=(ans+s[x])%mo;
					if(a[x]!=b[x])
						printf("%lld\n",(ans+1)%mo);
					else
						printf("%lld\n",ans);
				}
			}
		}
		else{
			for(int i=1;i<=n;i++){
				if(i+1==po){
					if(a[i]%2==1){
						s[i]=(s[i-1]+a[i])%mo;
						b[i]=a[i];
					}
					else{
						s[i]=(s[i-1]+a[i]-1)%mo;
						b[i]=a[i]-1;
					}
				}
				else{
					if(i==po){
						s[i]=s[i-1];
						b[i]=0;
					}
					else if(i!=n){
						if(a[i]%2==0){
							s[i]=(s[i-1]+a[i])%mo;
							b[i]=a[i];
						}
						else{
							s[i]=(s[i-1]+a[i]-1)%mo;
							b[i]=a[i]-1;
						}
					}
					else{
						if(a[i]%2==1){
							s[i]=(s[i-1]+a[i])%mo;
							b[i]=a[i];
						}
						else{
							s[i]=(s[i-1]+a[i]-1)%mo;
							b[i]=a[i]-1;
						}
					}
				}
			}
			scanf("%lld",&q);
			while(q--){
				int x,ans;
				scanf("%lld",&x);
				ans=s[n]*((x/n)%mo)%mo;
				x%=n;
				if(!x){
					if(a[n]!=b[n])
						printf("%lld\n",(ans+1)%mo);
					else
						printf("%lld\n",ans);
				}
				else{
					ans=(ans+s[x])%mo;
					if(x==po){
						if(a[x-1]%2==0)
							ans=(ans+2)%mo;
						printf("%lld\n",ans);
					}
					else{
						if(a[x]!=b[x])
							printf("%lld\n",(ans+1)%mo);
						else
							printf("%lld\n",ans);
					}
				}
			}
		}
		for(int i=1;i<=n+5;i++)
			a[i]=s[i]=b[i]=0;
	}
}

UNSQUERS
某个原题的加强版。
把一个点向它右边第一个>它的点连边,则形成了一棵树。
在一次询问内把区间所有点标记。
考虑扫描线,扫到i的时候处理以i为右端点的询问。
注意到一个点一定和一个被标记的叶子节点连通(就是只能通过标记的点到达)。
考虑维护一个点能够向上跳跳到标记点的最多次数\(a\),则答案就是a在区间[l,r]的最大值。
如果把树dfs一下,则每次对答案的贡献就是个区间+,使用线段树维护。
在线把线段树可持久化一下。

#include<bits/stdc++.h>
using namespace std;
#define N 200010
int n,m,a[N],bt[N],v[N*2],nxt[N*2],h[N],ec,ans[N],mn[N],rt[N];
void add(int x,int y){v[++ec]=y;nxt[ec]=h[x];h[x]=ec;}
void gt(int x,int y){for(;x;x-=x&-x)bt[x]=min(bt[x],y);}
int q(int x){int r=1e9;for(;x<100010;x+=x&-x)r=min(r,bt[x]);return r;}
struct tr{
	int tg[N*30],s[N*30],lc[N*30],rc[N*30],ct;
	void add(int &o,int p,int l,int r,int x,int y,int z){
		o=++ct;
		s[o]=s[p];
		tg[o]=tg[p];
		lc[o]=lc[p];
		rc[o]=rc[p];
		if(r<x||y<l)return;
		if(x<=l&&r<=y){
			tg[o]+=z;
			s[o]+=z;
			return;
		}
		int md=(l+r)/2;
		add(lc[o],lc[p],l,md,x,y,z);
		add(rc[o],rc[p],md+1,r,x,y,z);
		s[o]=max(s[lc[o]],s[rc[o]])+tg[o];
	}
	int q(int o,int l,int r,int x,int y,int va){
		if(r<x||y<l)return 0;
		if(x<=l&&r<=y)return s[o]+va;
		int md=(l+r)/2;
		return max(q(lc[o],l,md,x,y,va+tg[o]),q(rc[o],md+1,r,x,y,va+tg[o]));
	}
}sv;
void dfs(int x){
	mn[x]=x;
	for(int i=h[x];i;i=nxt[i]){
		dfs(v[i]);
		mn[x]=min(mn[x],mn[v[i]]);
	}
}
int main(){
	//freopen("exchange.in","r",stdin);
	//freopen("exchange.out","w",stdout);
	int t;
	scanf("%d%d%d",&n,&m,&t);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	memset(bt,63,sizeof(bt));
	n++;a[n]=100005;
	for(int i=n;i>=1;i--){
		int v=q(a[i]+1);
		if(i!=n)add(v,i);
		gt(a[i],i);
	}
	dfs(n);
	int p=1;
	for(int i=1;i<=n;i++)
		sv.add(rt[i],rt[i-1],1,n,mn[i],i,1);
	int la=0;
	for(int i=1;i<=m;i++){
		int l,r;
		scanf("%d%d",&l,&r);
		l=(l+t*la-1)%(n-1)+1;
		r=(r+t*la-1)%(n-1)+1;
		if(l>r)
			swap(l,r);
		printf("%d\n",la=sv.q(rt[r],1,n,l,r,0));
	}
}

SCALSUM
显然可以树上莫队,但是时间很卡。
考虑一种常数更小的方法。
\(s_i\)为深度为\(i\)的点的个数。
维护一个点\(i\)跳到上面第一次跳到\(<\sqrt{n},\geq sqrt{n}\)的点\(f_i,g_i\)
暴力跳\(g\),维护\(s_{i,j,k}\)表示深度为\(k\)的点,\(i,j\)的贡献。
然后\(s_{i,j,k}\)能够通过\(s_{i,j,l}\)\(l\)\(k\)上面第一个\(s\)\(<\sqrt{n}\)的点)轻松得来。

#include<bits/stdc++.h>
#define int unsigned int
//#pragma GCC optimize(3)
using namespace std;
#define N 600010
int n,q,w[N],v[N],nxt[N],h[N],ec,d[N],cc[N],id[N],ans[N],f[N],tp[600][600],nw[600][600],tt[N],ff[N],bs,g[N];
void add(int x,int y){
	v[++ec]=y;
	nxt[ec]=h[x];
	h[x]=ec;
}
struct no{
	int x,y,id;
};
vector<no>vc[N];
vector<int>dc[N];
int rd(){
	int x=0;
	char c=getchar();
	while(!isdigit(c))
		c=getchar();
	while(isdigit(c)){
		x=x*10u+c-'0';
		c=getchar();
	}
	return x;
}
void d1(int x,int fa){
	dc[d[x]].push_back(x);
	ff[x]=fa;
	cc[d[x]]++;
	for(int i=h[x];i;i=nxt[i])
		if(v[i]!=fa){
			d[v[i]]=d[x]+1;
			d1(v[i],x);
		}
}
void d2(int x,int fa){
	for(int i=h[x];i;i=nxt[i])
		if(v[i]!=fa){
			if(cc[d[v[i]]]<=bs)
				f[v[i]]=f[x];
			else
				f[v[i]]=v[i];
			if(cc[d[v[i]]]>bs)
				g[v[i]]=g[x];
			else
				g[v[i]]=v[i];
			d2(v[i],x);
		}
}
signed main(){
	//freopen("r.in","r",stdin);
	//freopen("w2.out","w",stdout);
	n=rd();
	q=rd();
	for(int i=1;i<=n;i++)
		w[i]=rd();
	for(int i=1;i<n;i++){
		int x=rd(),y=rd();
		add(x,y);
		add(y,x);
	}
	bs=sqrt(n);
	g[1]=1;
	d1(1,0);
	d2(1,0);
	for(int i=1;i<=q;i++){
		int x=rd(),y=rd();
		if(g[x])
			vc[d[g[x]]].push_back((no){g[x],g[y],i});
		x=f[x];
		y=f[y];
		while(x){
			ans[i]+=w[x]*w[y];
			x=f[ff[x]];
			y=f[ff[y]];
		}
	}
	for(int i=0;i<=n;i++)
		if(cc[i]<=bs){
			int ct=0,s=dc[i].size();
			for(int j=0;j<s;j++){
				tt[++ct]=dc[i][j];
				id[dc[i][j]]=ct;
			}
			for(int j=1;j<=ct;j++)
				for(int k=1;k<=ct;k++){
					int a=tt[j],b=tt[k],c=g[ff[a]],d=g[ff[b]];
					nw[id[a]][id[b]]=w[a]*w[b]+tp[id[c]][id[d]];
				}
			for(no x:vc[i])
				ans[x.id]+=nw[id[x.x]][id[x.y]];
			for(int j=1;j<=ct;j++)
				for(int k=1;k<=ct;k++){
					tp[j][k]=nw[j][k];
					nw[j][k]=0;
				}
		}
	for(int i=1;i<=q;i++)
		printf("%u\n",ans[i]);
}

CHEFSSM
\(\sum E(p)*i=\sum E(p>=i)\),其中\(p\)为一个局面能够转到正确位置的最小步数。
实际上我们要求的是对于\(0\leq x\leq p\)\(x\)\((2x+v_1)(2x+v_2)...(2x+v_n)\)的和。
把这个式子用分治\(fft\)展开,发现我们要求若干个底数相同但是幂数不同的自然数幂和。
使用伯努利数计算。求自然数幂和的公式也可以用卷积优化。

#include<bits/stdc++.h>
using namespace std;
#define mo 998244353
#define N 300010
#define int long long
#define ll unsigned long long
#define pl vector<int>
int qp(int x,int y){
	int r=1;
	for(;y;y>>=1,x=1ll*x*x%mo)
		if(y&1)r=1ll*r*x%mo;
	return r;
}
int n,k,rev[N],v,c,le,w[N];
ll b[N];
void deb(pl x){
	for(int i:x)cout<<i<<' ';
	puts("");
}
void init(int n){
	v=1;
	le=0;
	while(v<n)le++,v*=2;
	for(signed i=0;i<v;i++)
		rev[i]=(rev[i>>1]>>1)|((i&1)<<(le-1));
	int g=qp(3,(mo-1)/v);
	w[v/2]=1;
	for(int i=v/2+1;i<v;i++)
		w[i]=1ull*w[i-1]*g%mo;
	for(signed i=v/2-1;~i;i--)
		w[i]=w[i*2];
}
void fft(int v,pl &a,int t){
	static unsigned long long b[N];
	int s=le-__builtin_ctz(v);
   	for(int i=0;i<v;i++)
   		b[rev[i]>>s]=a[i];
	int c=0;
	w[0]=1;
    for(signed i=1;i<v;i*=2,c++)
    	for(signed r=i*2,j=0;j<v;j+=r)
            for(signed k=0;k<i;k++){
               	int tx=b[j+i+k]*w[k+i]%mo;
            	b[j+i+k]=b[j+k]+mo-tx;
            	b[j+k]+=tx;
            }
    for(int i=0;i<v;i++)
    	a[i]=b[i]%mo;
    if(t==0)return;
    int iv=qp(v,mo-2);
    for(signed i=0;i<v;i++)
    	a[i]=1ull*a[i]*iv%mo;
    a.resize(v);
    reverse(a.begin()+1,a.end());
}
pl operator *(pl x,pl y){
	int s=x.size()+y.size()-1;
	init(s);
	x.resize(v);
	y.resize(v);
	fft(v,x,0);
	fft(v,y,0);
	for(int i=0;i<v;i++)
		x[i]=x[i]*y[i]%mo;
	fft(v,x,1);
	x.resize(s);
	return x;
}
void inv(int n,pl &b,pl &a){
	if(n==1){
		b[0]=qp(a[0],mo-2);
		return;
	}
	inv((n+1)/2,b,a);
    static pl c;
	init(n*2);
	c.resize(v);
	b.resize(v);
    for(int i=0;i<n;i++)
		c[i]=a[i];
    fft(v,c,0);
    //deb(c);
	fft(v,b,0);
	//deb(b);
    for(int i=0;i<v;i++)
    	b[i]=1ll*(2ll-1ll*c[i]*b[i]%mo+mo)%mo*b[i]%mo;
    //deb(b);
    fft(v,b,1);
    //deb(b);
   	b.resize(n);
   	//deb(b);
}
void ad(pl &x,pl y,int l){
	x.resize(max((int)x.size(),(int)y.size()+l));
	for(int i=0;i<y.size();i++)
		x[i+l]=(x[i+l]+y[i])%mo;
}
pl operator +(pl x,pl y){
	ad(x,y,0);
	return x;
}
pl iv(pl x){
	pl y;
	int n=x.size();
	y.resize(n);
	inv(n,y,x);
	y.resize(n);
	return y;
}
int a[N],T,B[N],va[N];
pl fz(int l,int r){
	if(l==r){
		pl va;
		va.resize(2);
		va[1]=mo-2;
		va[0]=a[l]+1;
		return va;
	}
	int md=(l+r)/2;
	return fz(l,md)*fz(md+1,r);
}
void cal(int v,int x){
	pl a,b;
	a.resize(x+2);
	b.resize(x+2);
	va[0]=v;
	int vv=1;
	for(int i=0;i<=x+1;i++){
		a[i]=B[i]*qp(vv,mo-2)%mo;
		vv=vv*(i+1)%mo;
	}
	vv=1;
	for(int i=0;i<=x+1;i++){
		b[i]=qp(v+1,i+1)*qp(vv,mo-2)%mo;
		vv=vv*(i+2)%mo;
	}
	a=a*b;
	vv=1;
	for(int i=1;i<=x;i++){
		va[i]=vv*a[i]%mo;
		vv=vv*(i+1)%mo;
	}
	va[0]=v;
}
signed main(){
	scanf("%lld",&T);
	while(T--){
		scanf("%lld",&n);
		int mn=1e18,vv=1;
		for(int i=1;i<=n;i++){
			scanf("%lld",&a[i]);
			mn=min(mn,a[i]);
			va[i]=a[i];
			vv=vv*(a[i]+1)%mo;
		}
		pl y;
		y.resize(n+1);
		int x=1;
		for(int i=0;i<=n;i++){
			x=x*(i+1)%mo;
			y[i]=qp(x,mo-2)%mo;
		}
		y=iv(y);
		x=1;
		for(int i=0;i<=n;i++){
			B[i]=y[i]*x%mo;
			x=x*(i+1)%mo;
		}
		B[0]=1;
		pl z=fz(1,n);
		cal((mn+1)/2,n);
		int ans=0;
		for(int i=0;i<=n;i++)
			ans=(ans+va[i]*z[i]%mo)%mo;
		printf("%lld\n",ans*qp(vv,mo-2)%mo);
	}
}

RB2CNF
显然我们求出一个合法的染色方案后就是个经典的二元关系网络流。
然后求出合法的染色方案可以使用二分图染色。

posted @ 2020-11-13 22:17  celerity1  阅读(79)  评论(0编辑  收藏  举报