20230912 比赛总结

反思

B

有一些细节没有想好就放掉了,需要更加严谨一些

D

有点套路的分块,想了一半就没继续想,且没有想到自己做过的一个简单的询问中的做法,有点失败

题解

比赛链接

A

简单题

B

套路题
能够走到至少一个公共点难办,所以考虑容斥,计算 1 1 1 2 2 2 不能到达任何一个点的方案数
考虑枚举 1 1 1 2 2 2 都无法到达的子集,令其为 S 1 S1 S1,然后枚举 1 1 1 能到达的子集,令其为 S 2 S2 S2,这样可以得出 2 2 2 能够到达的子集 S 3 S3 S3,所以 S 1 S1 S1 S 2 , S 3 S2,S3 S2,S3 之间的边是定向了的, S 1 S1 S1 内部的边是可以随便定向的, S 2 , S 3 S2,S3 S2,S3 内部的边可以通过另一个 d p dp dp 求解
考虑令 f [ S ] f[S] f[S] 1 1 1 可以到达点集 S S S 的方案数,同样考虑容斥,接下来就很套路了
考虑将 1 , 2 1,2 1,2 的点移出枚举的点击,那么时间复杂度就变成了 O ( 3 n − 2 ) O(3^{n-2}) O(3n2)

#include 
using namespace std;
const int N=16,M=300,P=1e9+7;
int n,m,f[1<<N],g[1<<N],cover[1<<N],lines[1<<N];
int pw2[M];
int e[M],ne[M],h[N],idx;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
int lowbit(int x){ return x&-x;}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int trans(int S){
    for(int i=0;i<=m;i++) if(S==pw2[i]) return i;
}
int main(){
    freopen("moviestar.in","r",stdin);
    freopen("moviestar.out","w",stdout);
	n=read(),m=read();
	pw2[0]=1;
	for(int i=1;i<=m;i++) pw2[i]=pw2[i-1]*2%P;
    memset(h,-1,sizeof(h));
	for(int i=1;i<=m;i++){
		int x=read(),y=read();x--,y--;
		if(x>y) swap(x,y);
		add(x,y),add(y,x);
		if(x==0&&y==1){ printf("%d\n",pw2[m]);exit(0);}
	}
	//处理出每个点集内的边的数量
	for(int S=1;S<1<<n;S++){
		lines[S]=lines[S-lowbit(S)];
		int lb=trans(lowbit(S));
		for(int i=h[lb];~i;i=ne[i]) if(S>>e[i]&1) lines[S]++;
	}
	//处理出0可以到达的点的集合的方案数
	f[0]=1;
	for(int S=1;S<1<<(n-2);S++){
		f[S]=pw2[lines[(S<<2)^1]];
		// cout<
		for(int T=S;;T=(T-1)&S){
			if(S!=T) f[S]=(f[S]-1ll*f[T]*pw2[lines[(S^T)<<2]])%P;
			if(!T) break;
		}
	}
	//处理出1可以到达的点的集合的方案数
	g[0]=1;
	for(int S=1;S<1<<(n-2);S++){
		g[S]=pw2[lines[(S<<2)^2]];
		for(int T=S;;T=(T-1)&S){
			if(S!=T) g[S]=(g[S]-1ll*g[T]*pw2[lines[(S^T)<<2]])%P;
			if(!T) break;
		}
		// cout<
	}
    // cout<
	//预处理出每个点集连接的点
	for(int S=0;S<1<<n;S++){
		for(int i=0;i<n;i++) if(S>>i&1)
			for(int j=h[i];~j;j=ne[j]) cover[S]|=1<<e[j];
	}
	//容斥求答案
	int full=(1<<(n-2))-1,ans=0;
	for(int S=0;S<1<<(n-2);S++){//0和1都无法到达的点集
		int nS=full^S;
		for(int T=nS;;T=(T-1)&nS){//T为0能都走到的点
            // cerr<
			if((cover[(T<<2)^1]&(((nS^T)<<2)^2))>0){
                if(!T) break;
                continue;
            }
            // cerr<
			ans=(ans+1ll*f[T]*g[nS^T]%P*pw2[lines[S<<2]])%P;
            if(!T) break;
		}
	}
	ans=(pw2[m]-ans+P)%P;
	printf("%d\n",ans);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}
//g++ -std=c++11 -O2 -o 1.exe B.cpp

C

好神啊!
神仙操作1:
根据哥德巴赫猜想,任何一个 ≥ 6 \ge 6 6 的偶数都可以拆分成两个奇质数的和,这在 n ≤ 1 0 7 n\le 10^7 n107 时是成立的
所以易得出:
操作 1 1 1:长度为奇质数的段需要操作 1 1 1
操作 2 2 2:长度为偶数的段需要操作 2 2 2
操作 3 3 3:长度为奇合数的段需要操作 3 3 3
神仙操作2:
把原序列异或差分,那么每次就变成只要修改 2 2 2 个数,之间距离为奇质数,使差分序列清 0 0 0
这样最优的操作一定是使奇质数之间的匹配尽量多
为什么?考虑只有操作 3 3 3 可能取代掉操作 1 1 1,但可以发现操作 3 3 3 只会操作至多一次,且一个奇合数必定可以用 3 3 3 次的长度为奇质数的段的取完
然后考虑奇偶连边,跑二分图匹配即可

时间复杂度 O ( k 3 ) O(k^3) O(k3)(二分图匹配绝对跑不满
原题:arc180f

#include 
using namespace std;
const int N=4100,V=10000100;
int n,cf[V];
int pr[V],v[V],tot;
bool ispr[V],vis[N];
int nodes[N],cnt,match[N];
int h[N],e[N*N],ne[N*N],idx;
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
void sieve(int n){
	for(int i=2;i<=n;i++){
		if(!v[i]) v[i]=i,ispr[i]=1,pr[++tot]=i;
		for(int j=1;j<=tot&&pr[j]<=n/i;j++){
			v[pr[j]*i]=pr[j];
			if(v[i]==pr[j]) break;
		}
	}
	ispr[2]=0;
}
bool find(int x){
	for(int i=h[x];~i;i=ne[i]){
		int y=e[i];
		if(vis[y]) continue;
		vis[y]=1;
		if(!match[y]||find(match[y])){ match[y]=x;return 1;}
	}
    return 0;
}
void add(int x,int y){ e[idx]=y,ne[idx]=h[x],h[x]=idx++;}
int main(){
    freopen("oatmeal.in","r",stdin);
    freopen("oatmeal.out","w",stdout);
	sieve(V-1);
	n=read();
	for(int i=1;i<=n;i++){
		int x=read();
		cf[x]^=1,cf[x+1]^=1;
	}
	for(int i=0;i<V;i++) if(cf[i]) nodes[++cnt]=i;
    // for(int i=1;i<=cnt;i++) cout<
	memset(h,-1,sizeof(h));
	int o=0,e=0;
	for(int i=1;i<=cnt;i++) o+=nodes[i]&1,e+=~nodes[i]&1;
	for(int i=1;i<=cnt;i++) for(int j=1;j<=cnt;j++)
		if(nodes[j]>nodes[i]&&ispr[nodes[j]-nodes[i]]) add(i,j+cnt),add(j,i+cnt);
	int ans=0;
	for(int i=1;i<=cnt;i++){
		memset(vis,0,sizeof(vis));
		if(find(i)) ans++;
	}
    ans/=2;
    // cout<
	printf("%d\n",ans+(o-ans)/2*2+(e-ans)/2*2+((o-ans)&1)*3);
	fprintf(stderr,"%d ms\n",int(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

D

考虑拆分操作贡献
[ x ∼ y ] → [ x ∼ y ] [x\sim y]\to [x\sim y] [xy][xy] 可以拆分成 [ 1 ∼ y ] → [ x ∼ y ]        −        [ 1 ∼ x − 1 ] → [ x ∼ y ] [1\sim y]\to [x\sim y]\;\;\;-\;\;\;[1\sim x-1]\to [x\sim y] [1y][xy][1x1][xy]
[ 1 ∼ y ] → [ x ∼ y ] [1\sim y]\to [x\sim y] [1y][xy] 是好求的,一遍线段树即可
考虑后面的式子如何求
继续拆分贡献
[ 1 ∼ x − 1 ] → [ x ∼ y ]    ⟺    [ 1 ∼ x − 1 ] → [ 1 ∼ y ]        −        [ 1 ∼ x − 1 ] → [ 1 ∼ x − 1 ] [1\sim x-1]\to [x\sim y]\iff [1\sim x-1]\to [1\sim y]\;\;\;-\;\;\;[1\sim x-1]\to [1\sim x-1] [1x1][xy][1x1][1y][1x1][1x1]
注意,后面的贡献是可以不区分先后的,即可以先做完所有修改再做询问
这样直接上莫队就可以了
时间复杂度 O ( m k l o g n ) O(m\sqrt klogn) O(mk logn),我只能拿 85 p t s 85pts 85pts
考虑 w x q wxq wxq 的一种序列分块做法,考虑每个修改的贡献,这是可以做到不带 l o g log log 的,但我不想写了,以后有空再补

85 p t s 85pts 85pts 代码:

#include 
#define lowbit(x) x&-x
typedef long long LL;
using namespace std;
const int N=200100,M=200100,K=200100;
struct Node{ int op,x,y,v;}q[M];
struct Query{ int l,r,id;}qu[K];
int n,m,k,a[N];
LL res,sum[N],sum2[N],ans[N];
int pos[N];
inline int read(){
	int FF=0,RR=1;
	char ch=getchar();
	for(;!isdigit(ch);ch=getchar()) if(ch=='-') RR=-1;
	for(;isdigit(ch);ch=getchar()) FF=(FF<<1)+(FF<<3)+ch-48;
	return FF*RR;
}
bool cmp(const Query &x,const Query &y){
	if(pos[x.l]^pos[y.l]) return pos[x.l]<pos[y.l];
	return pos[x.l]&1?x.r<y.r:x.r>y.r;
}
struct SegmentTree{
	LL tr1[N],tr2[N];
	void ADD(int x,int val){
		for(int i=x;i<=n;i+=lowbit(i)) tr1[i]+=val,tr2[i]+=1ll*val*x;
	}
	void add(int l,int r,int val){
		ADD(l,val);
		if(r<n) ADD(r+1,-val);
	}
	LL ASK(int x){
		if(!x) return 0;
		LL res=0;
		for(int i=x;i;i-=lowbit(i)) res+=(x+1)*tr1[i]-tr2[i];
		return res;
	}
	LL ask(int l,int r){ return ASK(r)-ASK(l-1);}
}sg1,sg2,sg3;
void add1(int pos){
	if(q[pos].op==1){
		res+=sg2.ask(q[pos].x,q[pos].y)*q[pos].v;
		sg1.add(q[pos].x,q[pos].y,q[pos].v);
	}
}
void del1(int pos){
	if(q[pos].op==1){
		res-=sg2.ask(q[pos].x,q[pos].y)*q[pos].v;
		sg1.add(q[pos].x,q[pos].y,-q[pos].v);
	}
}
void add2(int pos){
	if(q[pos].op==2){
		res+=sg1.ask(q[pos].x,q[pos].y);
		sg2.add(q[pos].x,q[pos].y,1);
	}
}
void del2(int pos){
	if(q[pos].op==2){
		res-=sg1.ask(q[pos].x,q[pos].y);
		sg2.add(q[pos].x,q[pos].y,-1);
	}
}
signed main(){
    freopen("milktea.in","r",stdin);
    freopen("milktea.out","w",stdout);
	n=read(),m=read(),k=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1;i<=m;i++){
		q[i].op=read(),q[i].x=read(),q[i].y=read();
		if(q[i].op==1) q[i].v=read();
	}
	for(int i=1;i<=n;i++) sg3.add(i,i,a[i]);
	for(int i=1;i<=m;i++){
		sum[i]=sum[i-1];
		if(q[i].op==1) sg3.add(q[i].x,q[i].y,q[i].v);
		else sum[i]+=sg3.ask(q[i].x,q[i].y);
	}
    for(int i=1;i<=m;i++){
        sum2[i]=sum2[i-1];
        if(q[i].op==1){
            sum2[i]+=sg2.ask(q[i].x,q[i].y)*q[i].v;
            sg1.add(q[i].x,q[i].y,q[i].v);
        }
        else{
            sum2[i]+=sg1.ask(q[i].x,q[i].y);
            sg2.add(q[i].x,q[i].y,1);
        }
    }
    for(int i=1;i<=n;i++) sg1.tr1[i]=sg1.tr2[i]=sg2.tr1[i]=sg2.tr2[i]=0;
	for(int i=1;i<=k;i++){
		int x=read(),y=read();ans[i]=sum[y]-sum[x-1]+sum2[x-1];
        // cout<
		qu[i]={x-1,y,i};
	}
	int B=250;
	for(int i=1;i<=n;i++) pos[i]=(i-1)/B+1;
	sort(qu+1,qu+k+1,cmp);
	for(int cur=1,i=0,j=0;cur<=k;cur++){
		while(i<qu[cur].l) add1(++i);
		while(i>qu[cur].l) del1(i--);
		while(j<qu[cur].r) add2(++j);
		while(j>qu[cur].r) del2(j--);
		ans[qu[cur].id]-=res;
	}
	for(int i=1;i<=k;i++) printf("%lld\n",ans[i]);
	fprintf(stderr,"%d ms\n",int64_t(1e3*clock()/CLOCKS_PER_SEC));
	return 0;
}

你可能感兴趣的:(其他,算法)