2023.4.12拷逝

T1 seq

首先这道题要注意对题意的理解,子序列的意思是子集,而不是子区间。

我们可以将序列从小到大排序。排序后,如果一个子序列只有 \(a[i]\)\(a[j]\) \((i<j)\) 和任意多个在 \(a[i]\)\(a[j]\) 之间的数,那么这个子序列的权值就是 \(a[i]\times a[j]\) 。这样的序列有 \(2^{j-i-1}\)个。

由此可知, \(1-i\) 中包含 \(i\) 的所有子序列(不包括只有 \(a[i]\) 一个数的子序列)的权值和为\((2^{i-2}\times a[1]+2^{i-3}\times a[2]+...+a[i-1])\times a[i]\)。令 \(sum=2^{i-2}\times a[1]+2^{i-3}\times a[2]+...+a[i-1]\) ,在遍历这个序列的时候,可以通过 \(sum=sum\times 2+a[i-1]\) 来更新 \(sum\) ,用 \(ans=ans+a[i]\times sum\) 来更新 \(ans\)

\(code:\)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const long long mod=998244353;
long long p[1000005],n,a[1000005],ans,sum;
int main(){
	freopen("seq.in","r",stdin);
	freopen("seq.out","w",stdout);
	scanf("%lld",&n);
	for(int i=1;i<=n;++i)
		scanf("%lld",&a[i]);
	sort(a+1,a+n+1);
	p[0]=1;
	for(int i=1;i<=n;++i)
		p[i]=p[i-1]*2%mod;
	for(int i=1;i<=n;++i){
		sum=(sum*2%mod+a[i-1]%mod)%mod;
		ans=(ans+a[i]*sum%mod)%mod;
		ans=(ans+a[i]*a[i]%mod)%mod;
	}
	printf("%lld\n",ans);
	fclose(stdin);fclose(stdout);
	return 0;
}

T2 game

图的形态只有两种,一种是初始形态,另一种是边权全部取反的形态,所以可以考虑分层图。

第一层图只将权值为 \(1\) 的边连起来,第二层图只将权值为 \(0\) 的边连起来,如果点 \(i\) 有开关,那么再把 \(i\)\(i+n\) 连起来。

最后在这个分层图上跑 \(BFS\) 即可。

\(code:\)

#include<iostream>
#include<queue>
#include<cstdio>
using namespace std;
int n,m,k,u,v,w,p,tot,nxt[8000005],ver[8000005],head[8000005],hav[4000005],dis[4000005],ok;
struct node{
	int f,step;
};queue <node> q;
void add(int x,int y){
	nxt[++tot]=head[x];head[x]=tot;
	ver[tot]=y;
}
int main(){
	//freopen("game.in","r",stdin);
	//freopen("game.out","w",stdout);
	scanf("%d%d%d",&n,&m,&k);
	for(int i=1;i<=m;++i){
		scanf("%d%d%d",&u,&v,&w);
		if(w)
			add(u,v),add(v,u);
		else
			add(u+n,v+n),add(v+n,u+n);
	}
	for(int i=1;i<=k;++i)
		scanf("%d",&p),hav[p]=hav[p+n]=1;
	for(int i=2;i<=n*2;++i)
		dis[i]=1e9;
	q.push((node){1,0});
	while(!q.empty()){
		node x=q.front();q.pop();
		//cout<<x.f<<endl;
		if(x.step>dis[x.f])
			continue;
		if(x.f==n||x.f==n*2){
			printf("%d\n",min(dis[n],dis[n*2]));
			ok=1;
			break;
		}
		for(int i=head[x.f];i;i=nxt[i])
			if(x.step+1<dis[ver[i]]){
				dis[ver[i]]=x.step+1;
				q.push((node){ver[i],x.step+1});
			}
		if(hav[x.f]){
			if(x.f>n) x.f-=n;
			else x.f+=n;
			if(x.step>dis[x.f])
				continue;
			for(int i=head[x.f];i;i=nxt[i])
				if(x.step+1<dis[ver[i]]){
					dis[ver[i]]=x.step+1;
					q.push((node){ver[i],x.step+1});
				}
		}
	}
	if(!ok)
		printf("-1\n");
	fclose(stdin);fclose(stdout);
	return 0;
}

T3 count

看到这道题,可以考虑质因数分解。

先考虑只有一种质因数的数。设 \(f[i][j]\) 表示长度为 \(j\) ,最后一个数可以表示成为 \(p^i(p∈prime)\) 的数列的方案数。

考虑如何求这个数组。如果倒数第二个数也是 \(p^i\) ,那么此时的方案数相当于 \(f[i][j-1]\) ;如果倒数第二个数不是 \(p^i\) ,那么此时的方案数相当于 \(f[i-1][j]\) 。所以\(f[i][j]=f[i-1][j]+f[i][j-1]\)

对于一般形式的数,应该怎么求呢?先给结论:如果一个数 \(a\) 可以分解为\(p_1^{k_1}\times p_1^{k_2}\times ...\times p_n^{k_n}\) ,那么长度为 \(j\) ,最后一个数为 \(a\) 的数列有 \(f[k_1][j]\times f[k_2][j]\times ...f[k_n][j]\) 种。至于证明,可以感性地理解为不同的质因数之间是相互独立的。

综上,我们可以先求出 \(f\) 数组,然后筛质数,再然后枚举数列中最后一个数,将其质因数分解并统计答案。

\(code:\)

#include<iostream>
#include<cstdio>
using namespace std;
long long f[25][200005],n,m,tot,sum[200005],ans,p[200005];bool ok[200005];
//f[i][j]:i阶等差数列第j项 
const long long mod=998244353;
int main(){
	//freopen("count.in","r",stdin);
	//freopen("count.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;++i)
		f[0][i]=1;
	for(int i=1;i<=20;++i)
		for(int j=1;j<=n;++j)
			f[i][j]=(f[i][j-1]+f[i-1][j])%mod;
	//for(int i=1;i<=20;++i,cout<<endl)
	//	for(int j=1;j<=n;++j)
	//		cout<<f[i][j]<<" ";
	for(int i=2;i*i<=m;++i)
		if(!ok[i]){
			p[++tot]=i;
			for(int j=i+i;j*j<=m;j+=i)
				ok[j]=1;
		}
	//cout<<tot<<endl;
	//for(int i=1;i<=tot;++i)
	//	cout<<p[i]<<" ";cout<<endl;
	for(int i=1;i<=m;++i)
		sum[i]=1;
	for(int i=2;i<=m;++i){
		int tmp=i,cnt;
		//if(i%10000==0)cout<<i<<endl;
		for(int j=1;tmp>1&&j<=tot;++j){
			cnt=0;
			while(tmp%p[j]==0)
				tmp/=p[j],++cnt;
			sum[i]=(sum[i]*f[cnt][n])%mod;
		}
		if(tmp>1)
			sum[i]=(sum[i]*f[1][n])%mod;
	}
	for(int i=1;i<=m;++i)
		ans=(ans+sum[i])%mod;
	printf("%lld\n",ans);
	fclose(stdin);fclose(stdout);
	return 0;
}

T4 gift

如果 \(n\) 个数异或和为 \(0\) ,那么这 \(n\) 个数的二进制的任意一位上,必然有偶数个 \(1\)

\(f[i][j]\) 表示 \(n\) 个数的二进制的前 \(j\) 位的和为 \(i\) 的方案数。我们可以枚举当前这一位上有多少个 \(1\) ,得到状态转移方程:

\(f[i][j]=c[n][k]\times f[i-1][j-(1<<(k-1))]\) ,其中 \(c\) 数组为组合数。

我用的是记忆化搜索,当然也可以用 \(for\) 循环实现。

\(code:\)

#include<iostream>
#include<cstdio>
using namespace std;
long long n,m,ans,stk[50005],sum[50005],c[5005][5005],tot,f[5005][25];bool ok[5005][25];
const long long mod=998244353;//f[i][j]表示二进制前j位拼出i的方案数 
long long dfs(int x,int y){//二进制第x位(2^(x-1)),还剩下y 
	if(ok[y][x])
		return f[y][x];
	if(!y)
		return f[y][x]=1;
	if(x==0)
		return f[y][x]=0;
	for(int j=0;j<=n&&y-j*(1<<(x-1))>=0;j+=2){
		f[y][x]=(f[y][x]+c[n][j]*dfs(x-1,y-j*(1<<(x-1)))%mod)%mod;
	}
	ok[y][x]=1;
	return f[y][x];
}
int main(){
	freopen("gift.in","r",stdin);
	freopen("gift.out","w",stdout);
	sum[0]=1;
	scanf("%lld%lld",&n,&m);
	for(int i=0;i<=n;++i)
		c[i][0]=1;
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j)
			c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
	printf("%lld\n",dfs(14,m)); 
	fclose(stdin);fclose(stdout);
	return 0;
} 
posted @ 2023-04-14 20:20  andy_lz  阅读(37)  评论(0)    收藏  举报