5.13 考试修改和总结

先放题解吧,考的很悲桑

第一题组合计数类DP

缩点之后易得对于每个固定形态的树有ans=(s1^d1*s2^d2……)

然后我们知道把一个树的普吕弗序列乘起来得s1^(d1-1)*s2^(d2-1)……

然后ans=普吕弗序列乘积*(s1*s2……)

设普吕弗序列长度为L(题解中的n-2并不标准)

因为sigma(s)=n

所以ans=simga(普吕弗序列乘积)*(s1*s2……)

ans=n^L*(s1*s2……)

我们只需要求出来sigma((s1*s2……)),dp就可以了

设f[i][j]表示i个元素分成j个联通块,我们考虑1的连通块大小则有

f[i][j]=simga(f[i-k][j-1]*C[i-1][k-1]*(m-1)!/2*m))

其中(m-1)!/2是环内方案数,m是环长

最后注意不存在二元环以及整个是一个环的情况

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;

typedef long long LL;
const int maxn=210;
int n,mod;
int C[maxn][maxn];
int sum[maxn];
int f[maxn][maxn];

void Get_C(){
	C[0][0]=1;
	for(int i=1;i<=n;++i){
		C[i][0]=C[i][i]=1;
		for(int j=1;j<i;++j){
			C[i][j]=C[i-1][j-1]+C[i-1][j];
			if(C[i][j]>=mod)C[i][j]-=mod;
		}
	}return;
}
void Get_DP(){
	sum[1]=1;sum[3]=3;
	for(int i=4;i<=n;++i)sum[i]=sum[i-1]*i%mod;
	f[0][0]=1;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=i;++j){
			for(int k=1;k<=i;++k){
				f[i][j]=f[i][j]+1LL*f[i-k][j-1]*C[i-1][k-1]*sum[k]%mod;
				if(f[i][j]>=mod)f[i][j]-=mod;
			}
		}
	}
	int ans=1;
	for(int i=3;i<=n-1;++i)ans=ans*i%mod;
	int now=1;
	for(int i=2;i<=n;++i){
		ans=ans+1LL*f[n][i]*now%mod;
		if(ans>=mod)ans-=mod;
		now=now*n%mod;
	}printf("%d\n",ans);return;
}

int main(){
	freopen("land.in","r",stdin);
	freopen("land.out","w",stdout);
	scanf("%d%d",&n,&mod);
	Get_C();Get_DP();
	return 0;
}

至于第二题嘛,首先这是一道论文题,而且还是论文为了引入数位DP算法用的题目

所以这是一道很简单的题目

首先设n的长度为len,长度不为len的和很好计算,随便搞一搞就好了

之后我们考虑n的限制,分奇数偶数讨论

类似数位DP算贡献

如果len是偶数,那么每一位的符号是确定的,只需要知道出现的次数就可以了,扫一遍即可

如果len是奇数,相邻两个数互相抵消,只会剩下1,然后算有多少对就可以了

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
LL n;
int Num[22],L;
LL Solve(int n,int k){
	if(k&1){
		LL d=-1;
		for(int i=1;i<=n;++i)d=d*10;
		return d/2;
	}else{
		if(!(n&1))return 0;
		LL d=-45;
		for(int i=1;i<n;++i)d=d*10;
		return d;
	}
}
LL Get_num(LL pre,int n){
	int len=0,tmp=1;
	LL p=pre,sum=0;
	while(p){
		sum=sum+(p%10)*tmp;
		tmp=-tmp;len++;p/=10;
	}sum=sum*(-tmp);
	for(int i=1;i<=n;++i)sum=sum*10;
	LL ans=Solve(n,n+len);
	if(!((n+len)&1))ans+=sum;
	return ans;
}
LL Get_ans(LL n){
	if(n<10){
		LL ans=0;
		for(int i=1;i<=n;++i){
			if(i&1)ans+=i;
			else ans-=i;
		}return ans;
	}
	memset(Num,0,sizeof(Num));L=0;
	LL u=n;
	while(u)Num[++L]=u%10,u/=10;
	LL ans=5;
	for(int i=1;i<L;++i){
		for(int j=1;j<=9;++j){
			ans-=Get_num(j,i-1);
		}
	}
	LL pre=0;
	for(int i=L;i>=2;--i){
		int end=Num[i];
		for(int j=0;j<end;++j){
			if(pre)ans-=Get_num(pre,i-1);
			pre++;
		}pre*=10;
	}
	int tmp=Num[1];
	int t=-1;
	for(int i=0;i<=tmp;++i){
		memset(Num,0,sizeof(Num));L=0;
		LL now=pre+i;
		while(now){
			Num[++L]=now%10;
			now/=10;
		}
		for(int j=L;j>=1;--j){
			ans+=Num[j]*t;
			t=-t;
		}	
	}return ans;
}
int main(){
	freopen("count.in","r",stdin);
	freopen("count.out","w",stdout);
	while(scanf("%lld",&n)==1){
		if(!n)break;
		printf("%lld\n",Get_ans(n));
	}return 0;
}

第三题是个提交答案题,自己失误很大

首先第一个点爆搜写错了,第三个点没开long long,第十个点标程写错了

QAQ 一共炸掉了30分,真是悲桑

但是为什么失误这么大呢,显然是因为自己给题答只留了一个小时多一点的时间,其他时间用来check第二题和磕第一题了

如果按照CTSC我给题答留个两个多小时的话,应该能搞出50-60分

而且吐槽一句:这个题答太不标准了,没checker,没评分参数,不然我怎么会挂掉?

挂掉还是自己弱

第一个点爆搜就可以了,自己犯蠢挂了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
using namespace std;

const int maxn=200010;
int n,m;
int ans=0;
int val[maxn];
struct OP{
	char type;
	int u,v,val;
}c[maxn];

int Get_ans(int S){
	int ans=0;
	for(int i=0;i<n;++i)if(S>>i&1)ans+=val[i];
	for(int i=1;i<=m;++i){
		int u=c[i].u,v=c[i].v;
		if(c[i].type=='A'){
			if(S>>u&1){
				if(S>>v&1)ans+=c[i].val;
			}
		}else if(c[i].type=='B'){
			if((S>>u&1)||(S>>v&1))ans+=c[i].val;
		}else if(c[i].type=='C'){
			if(S>>u&1){
				if(!(S>>v&1))ans+=c[i].val;
			}
		}else if(c[i].type=='D'){
			if(S>>v&1){
				if(!(S>>u&1))ans+=c[i].val;
			}
		}else{
			if(!(S>>u&1)){
				if(!(S>>v&1))ans+=c[i].val;
			}
		}
	}return ans;
}

int main(){
	freopen("shell1.in","r",stdin);
	freopen("shell1.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=0;i<n;++i)scanf("%d",&val[i]);
	for(int i=1;i<=m;++i){
		c[i].type=getchar();
		while(c[i].type<'!')c[i].type=getchar();
		scanf("%d%d%d",&c[i].u,&c[i].v,&c[i].val);
		c[i].u--;c[i].v--;
	}
	for(int i=0;i<(1<<n);++i){
		ans=max(ans,Get_ans(i));
	}printf("%d\n",ans);
	return 0;
}

第二个点注意到所有的权都很大,所以只是让你去写高精度

你贪心的去取所有正权点,会发现所有正权边也会被取到

然后答案就显然了

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
using namespace std;

const int maxn=52;
int n,m,u,v;
char s[maxn];
struct big_num{
	int a[52],len;
	void init(){
		len=0;memset(a,0,sizeof(a));
		len=strlen(s+1);
		for(int i=len;i>=1;--i)a[len-i+1]=s[i]-'0';
	}
	void add(const big_num &A){
		len=max(len,A.len)+1;
		for(int i=1;i<=len;++i){
			a[i]=a[i]+A.a[i];
			if(a[i]>=10)a[i]-=10,a[i+1]++;
		}
		while(len>0&&a[len]==0)len--;
	}
	void print(){
		for(int i=len;i>=1;--i)printf("%d",a[i]);
	}
}ans,A;

int main(){
	freopen("shell2.in","r",stdin);
	freopen("shell2.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%s",s+1);
		if(s[1]=='-')continue;
		A.init();ans.add(A);
	}
	for(int i=1;i<=m;++i){
		char ch=getchar();
		while(ch<'!')ch=getchar();
		scanf("%d%d",&u,&v);
		scanf("%s",s+1);
		if(s[1]=='-')continue;
		A.init();ans.add(A);
	}ans.print();
	return 0;
}

第三个点你会发现边权都特别大,然后点权是-1

然后限制都是B,所以我们一定能取得所有的边,问题就转化成了用最少的点

限制是u,v两个之中至少有一个被选中,二分图匹配即可

注意开long long

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;

typedef long long LL;
const int maxn=200010;
int n,m,u,v,w;
LL ans=0;
int val[maxn];
int h[maxn],cnt=1;
int vis[maxn],tim;
int girl[maxn];
struct edge{
	int to,next;
}G[3000010];
void add(int x,int y){
	++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt;
}
bool find(int u){
	for(int i=h[u];i;i=G[i].next){
		int v=G[i].to;
		if(vis[v]==tim)continue;
		vis[v]=tim;
		if(!girl[v]||find(girl[v])){
			girl[v]=u;girl[u]=v;
			return true;
		}
	}return false;
}

int main(){
	freopen("shell3.in","r",stdin);
	freopen("shell3.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)scanf("%d",&val[i]);
	for(int i=1;i<=m;++i){
		char ch=getchar();
		while(ch<'!')ch=getchar();
		scanf("%d%d%d",&u,&v,&w);
		add(u,v);add(v,u);
		ans+=w;
	}
	for(int i=1;i<=n;++i){
		if(girl[i])continue;
		tim++;
		if(find(i))ans--;
	}printf("%lld\n",ans);
	return 0;
}

第四个点只有C限制,而且边权都很小且是负数

我们就发现我们一条边也不能选,限制变成了选u就必须选v,求最大点权和

最大权闭合子图即可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<queue>
using namespace std;

const int maxn=200010;
const int oo=0x7fffffff/3;
int n,m,S,T;
int u,v,w;
int ans=0;
int val[maxn];
int h[maxn],cnt=1;
int cur[maxn];
struct edge{
	int to,next,w;
}G[3000010];
queue<int>Q;
int vis[maxn];
void add(int x,int y,int z){
	cnt++;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt;
	++cnt;G[cnt].to=x;G[cnt].next=h[y];G[cnt].w=0;h[y]=cnt;
}
bool BFS(){
	for(int i=S;i<=T;++i)vis[i]=-1;
	Q.push(S);vis[S]=1;
	while(!Q.empty()){
		int u=Q.front();Q.pop();
		for(int i=h[u];i;i=G[i].next){
			int v=G[i].to;
			if(G[i].w>0&&vis[v]==-1){
				vis[v]=vis[u]+1;
				Q.push(v);
			}
		}
	}return vis[T]!=-1;
}
int DFS(int x,int f){
	if(x==T||f==0)return f;
	int w,used=0;
	for(int i=cur[x];i;i=G[i].next){
		if(vis[G[i].to]==vis[x]+1){
			w=f-used;
			w=DFS(G[i].to,min(G[i].w,w));
			G[i].w-=w;G[i^1].w+=w;
			if(G[i].w>0)cur[x]=i;
			used+=w;if(used==f)return used;
		}
	}
	if(!used)vis[x]=-1;
	return used;
}
void dinic(){
	while(BFS()){
		for(int i=S;i<=T;++i)cur[i]=h[i];
		ans-=DFS(S,oo);
	}return;
}
int main(){
	freopen("shell4.in","r",stdin);
	freopen("shell4.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)scanf("%d",&val[i]);
	for(int i=1;i<=m;++i){
		char ch=getchar();
		while(ch<'!')ch=getchar();
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,oo);
	}
	S=0;T=n+1;
	for(int i=1;i<=n;++i){
		if(val[i]>0)ans+=val[i],add(S,i,val[i]);
		else add(i,T,-val[i]);
	}
	dinic();
	printf("%d\n",ans);
	return 0;
}

第五个点我们观察数据会发现有一个点点权很大,一个点点权很小,其他点都是0

之后看边权,发现边权和都是负的,证明负的点权一定不能取

且正点权一定能取,由于边权都是负数,所以能不选就不选

因为全是C操作

可以把正权点看成S,负权点看成T,从S到T求最小割即可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<queue>
using namespace std;

const int maxn=200010;
const int oo=0x7fffffff;
int n,m,S,T;
int u,v,w;
int ans;
int val[maxn];
int h[maxn],cnt=1;
int cur[maxn];
struct edge{
	int to,next,w;
}G[3000010];
queue<int>Q;
int vis[maxn];
void add(int x,int y,int z){
	++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt;
	++cnt;G[cnt].to=x;G[cnt].next=h[y];G[cnt].w=0;h[y]=cnt;
}
bool BFS(){
	for(int i=1;i<=n;++i)vis[i]=-1;
	Q.push(S);vis[S]=1;
	while(!Q.empty()){
		int u=Q.front();Q.pop();
		for(int i=h[u];i;i=G[i].next){
			int v=G[i].to;
			if(G[i].w>0&&vis[v]==-1){
				vis[v]=vis[u]+1;
				Q.push(v);
			}
		}
	}return vis[T]!=-1;
}
int DFS(int x,int f){
	if(x==T||f==0)return f;
	int w,used=0;
	for(int i=cur[x];i;i=G[i].next){
		if(vis[G[i].to]==vis[x]+1){
			w=f-used;
			w=DFS(G[i].to,min(G[i].w,w));
			G[i].w-=w;G[i^1].w+=w;
			if(G[i].w>0)cur[x]=i;
			used+=w;if(used==f)return used;
		}
	}
	if(!used)vis[x]=-1;
	return used;
}
void dinic(){
	while(BFS()){
		for(int i=1;i<=n;++i)cur[i]=h[i];
		ans-=DFS(S,oo);
	}return;
}

int main(){
	freopen("shell5.in","r",stdin);
	freopen("shell5.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i){
		scanf("%d",&val[i]);
		if(val[i]>0)ans+=val[i];
	}
	for(int i=1;i<=m;++i){
		char ch=getchar();
		while(ch<'!')ch=getchar();
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,-w);
	}
	S=12345;T=98765;
	dinic();
	printf("%d\n",ans);
	return 0;
}

UPD:第九个点和第五个点一样,所以程序差不多

第六个点我们发现数据时完全随机无规律,所以只能用近似算法了

模拟退火乱搞即可,搞的并没有答案优QAQ

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define eps 1e-5
using namespace std;

const int maxn=200010;
const int oo=0x7fffffff;
int n,m,ans;
int val[maxn];
bool vis[maxn];
struct OP{
	char type;
	int u,v,val;
}c[maxn];
double R(){return rand()%10000/10000.0;}
int Get_ans(){
	int sum=0;
	for(int i=1;i<=n;++i)if(vis[i])sum+=val[i];
	for(int i=1;i<=m;++i){
		int u=c[i].u,v=c[i].v;
		if(c[i].type=='A'){
			if(vis[u]&&vis[v])sum+=c[i].val;
		}else if(c[i].type=='B'){
			if(vis[u]||vis[v])sum+=c[i].val;
		}else if(c[i].type=='C'){
			if(vis[u]&&!vis[v])sum+=c[i].val;
		}else if(c[i].type=='D'){
			if(!vis[u]&&vis[v])sum+=c[i].val;
		}else if(!vis[u]&&!vis[v])sum+=c[i].val;
	}return sum;
}
void SA(double T){
	int tmp=0,cur=0;
	for(int i=1;i<=n;++i)vis[i]=rand()%2;
	cur=Get_ans();
	while(T>eps){
		int now=rand()%n+1;
		vis[now]^=1;
		tmp=Get_ans();
		ans=max(ans,tmp);
		int d=tmp-cur;
		if(d>0||exp(d/T)>R())cur=tmp;
		else vis[now]^=1;
		T*=0.998;
	}return;
}

int main(){
	freopen("shell6.in","r",stdin);
	freopen("shell6.out","w",stdout);
	srand(5211314);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)scanf("%d",&val[i]);
	for(int i=1;i<=m;++i){
		c[i].type=getchar();
		while(c[i].type<'!')c[i].type=getchar();
		scanf("%d%d%d",&c[i].u,&c[i].v,&c[i].val);	
	}
	int T=1000;ans=-oo;
	while(T--)SA(10000);
	printf("%d\n",ans);
	return 0;
}

第七个点和第八个点数据都做错了,不过我还是写了标程的

很容易发现他想要做的数据是分块的,然后分块模拟退火即可

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#define eps 1e-5
using namespace std;

const int maxn=200010;
const int oo=0x7fffffff;
int n,m,ans,Ans;
int val[maxn];
bool vis[maxn];
struct OP{
	char type;
	int u,v,val;
}c[maxn];
double R(){return rand()%10000/10000.0;}
int Get_ans(){
	int sum=0;
	for(int i=1;i<=100;++i)if(vis[i])sum+=val[i];
	for(int i=1;i<=m;++i){
		int u=c[i].u,v=c[i].v;
		if(c[i].type=='A'){
			if(vis[u]&&vis[v])sum+=c[i].val;
		}else if(c[i].type=='B'){
			if(vis[u]||vis[v])sum+=c[i].val;
		}else if(c[i].type=='C'){
			if(vis[u]&&!vis[v])sum+=c[i].val;
		}else if(c[i].type=='D'){
			if(!vis[u]&&vis[v])sum+=c[i].val;
		}else if(!vis[u]&&!vis[v])sum+=c[i].val;
	}return sum;
}
void SA(double T){
	int tmp=0,cur=0;
	for(int i=1;i<=100;++i)vis[i]=rand()%2;
	cur=Get_ans();
	while(T>eps){
		int now=rand()%100+1;
		vis[now]^=1;
		tmp=Get_ans();
		ans=max(ans,tmp);
		int d=tmp-cur;
		if(d>0)cur=tmp;
		else vis[now]^=1;
		T*=0.9;
	}return;
}

int main(){
	freopen("shell7.in","r",stdin);
	freopen("shell7.out","w",stdout);
	srand(5211314);
	scanf("%d%d",&n,&m);
	m/=1000;
	for(int i=1;i<=n;++i)scanf("%d",&val[i]);
	for(int k=1;k<=1000;++k){
		for(int i=1;i<=m;++i){
			c[i].type=getchar();
			while(c[i].type<'!')c[i].type=getchar();
			scanf("%d%d%d",&c[i].u,&c[i].v,&c[i].val);
			c[i].u=c[i].u%100+1;c[i].v=c[i].v%100+1;	
		}
		ans=-oo;int T=300;
		while(T--)SA(10000);
		Ans+=ans;
		for(int i=1;i+100<=n;++i)val[i]=val[i+100];
	}
	printf("%d\n",Ans);
	return 0;
}

第十个点m=n-1,观察边很容易发现这是棵树

设f[i][0/1]表示选不选,然后做一遍树形DP就可以了

讨论的时候注意细节,连std都讨论错了

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
const int maxn=200010;
int n,m;
int u,v,w;
int h[maxn],cnt=0;
int val[maxn];
LL f[maxn][2];
struct edge{
	char type;
	int to,next,w;
}G[maxn];
void add(int x,int y,int z,char c){
	cnt++;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;G[cnt].type=c;h[x]=cnt;
}
void DP(int u){
	f[u][0]=0;f[u][1]=val[u];
	for(int i=h[u];i;i=G[i].next){
		int v=G[i].to;
		DP(v);
		if(G[i].type=='A'){
			f[u][0]=f[u][0]+max(f[v][0],f[v][1]);
			f[u][1]=f[u][1]+max(f[v][0],f[v][1]+G[i].w);
		}else if(G[i].type=='B'){
			f[u][0]=f[u][0]+max(f[v][0],f[v][1]+G[i].w);
			f[u][1]=f[u][1]+G[i].w+max(f[v][0],f[v][1]);
		}else if(G[i].type=='C'){
			f[u][0]=f[u][0]+max(f[v][0],f[v][1]);
			f[u][1]=f[u][1]+max(f[v][0]+G[i].w,f[v][1]);
		}else if(G[i].type=='D'){
			f[u][0]=f[u][0]+max(f[v][0],f[v][1]+G[i].w);
			f[u][1]=f[u][1]+max(f[v][0],f[v][1]);
		}else{
			f[u][0]=f[u][0]+max(f[v][0]+G[i].w,f[v][1]);
			f[u][1]=f[u][1]+max(f[v][1],f[v][0]);
		}
	}return;
}

int main(){
	freopen("shell10.in","r",stdin);
	freopen("shell10.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i)scanf("%d",&val[i]);
	for(int i=1;i<=m;++i){
		char ch=getchar();
		while(ch<'!')ch=getchar();
		scanf("%d%d%d",&u,&v,&w);
		if(u>v){
			swap(u,v);
			if(ch=='B')ch='C';
			else if(ch=='C')ch='B';
		}
		add(u,v,w,ch);
	}
	DP(1);
	printf("%lld\n",max(f[1][0],f[1][1]));
	return 0;
}

  

posted @ 2016-05-13 16:45  _Vertical  阅读(173)  评论(0编辑  收藏  举报