2023.4.12拷逝

T1 seq

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

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

由此可知, 1i 中包含 i 的所有子序列(不包括只有 a[i] 一个数的子序列)的权值和为(2i2×a[1]+2i3×a[2]+...+a[i1])×a[i]。令 sum=2i2×a[1]+2i3×a[2]+...+a[i1] ,在遍历这个序列的时候,可以通过 sum=sum×2+a[i1] 来更新 sum ,用 ans=ans+a[i]×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 有开关,那么再把 ii+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 ,最后一个数可以表示成为 pi(pprime) 的数列的方案数。

考虑如何求这个数组。如果倒数第二个数也是 pi ,那么此时的方案数相当于 f[i][j1] ;如果倒数第二个数不是 pi ,那么此时的方案数相当于 f[i1][j] 。所以f[i][j]=f[i1][j]+f[i][j1]

对于一般形式的数,应该怎么求呢?先给结论:如果一个数 a 可以分解为p1k1×p1k2×...×pnkn ,那么长度为 j ,最后一个数为 a 的数列有 f[k1][j]×f[k2][j]×...f[kn][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]×f[i1][j(1<<(k1))] ,其中 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 @   andy_lz  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示