【18国庆训练赛1】2017-2018 ACM-ICPC, Asia Daejeon Regional Contest

本文介绍了2017-2018 ACM-ICPC亚洲大田区域赛的比赛题目,包括B题的Connect3,C题的Game Map,D题的Happy Number等。针对每个题目,提供了简要的题意解析和解题思路,涉及DFS、BFS、拓扑排序、暴力求解、网络流等算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://vjudge.net/contest/258556

B Connect3

题意:两人在4*4的棋盘里下棋,先手执黑第一步必下在(1,x),所有棋子只能下在第一行或者另一个棋子的上面。问:有多少种后手胜且最后一步下在(a,b)的方案。(不考虑下棋过程)

题解:因为条件严苛(轮流下,下棋位置少),所以合法情况不多,DFS和BFS都行,直接3进制判重即可。

#include<bits/stdc++.h>

using namespace std;

int Fr,EX,EY;
bool b[50000001];
int a[30][30];
int dl[11093001];
bool dll[11093001];
int Ans;

bool Win(int x,int y,int z)
{
	a[x][y]=z;
	for(int i=1;i<=4;i++)
	{
		if(a[i][1]==z&&a[i][2]==z&&a[i][3]==z) {a[x][y]=0;return true;}
		if(a[i][4]==z&&a[i][2]==z&&a[i][3]==z) {a[x][y]=0;return true;}
		if(a[1][i]==z&&a[2][i]==z&&a[3][i]==z) {a[x][y]=0;return true;}
		if(a[4][i]==z&&a[2][i]==z&&a[3][i]==z) {a[x][y]=0;return true;}
	}
	for(int i=2;i<=3;i++)
		for(int j=2;j<=3;j++)
		{
			if(a[i][j]==z&&a[i+1][j+1]==z&&a[i-1][j-1]==z) {a[x][y]=0;return true;}
			if(a[i][j]==z&&a[i+1][j-1]==z&&a[i-1][j+1]==z) {a[x][y]=0;return true;}
		}
	a[x][y]=0;
	return false;
}

int Rec()
{
	int Sum=0;
	for(int i=1;i<=4;i++)
		for(int j=1;j<=4;j++)
			Sum=Sum*3+a[i][j];
	return Sum;
}
void Out(int x)
{
	int X=x;
	for(int i=4;i>=1;i--)
		for(int j=4;j>=1;j--)
		{
			a[i][j]=X%3;
			X/=3;
		}
}


int main()
{
	scanf("%d%d%d",&Fr,&EX,&EY);
	a[1][Fr]=1;
	int Now=Rec();
	int t=0,w=1;
	dl[1]=Now;
	b[Now]=true;
	dll[1]=true;
	do
	{
		t++;
		Out(dl[t]);
		
		for(int j=1;j<=4;j++)
		{
			for(int i=1;i<=4;i++)
			{
				if(a[i][j]==0)
				{
					if(dll[t])
					{
						if(Win(i,j,2))
						{
							if(i==EX&&j==EY)
							{
								Ans++;
							}
						}
						else
						{
							a[i][j]=2;
							Now=Rec();
							if(!b[Now]) b[Now]=true,dl[++w]=Now,dll[w]=false;
							a[i][j]=0;
						}
					}
					else
					{
						if(!Win(i,j,1))
						{
							a[i][j]=1;
							Now=Rec();
							if(!b[Now]) b[Now]=true,dl[++w]=Now,dll[w]=true;
							a[i][j]=0;
						}
					}
					break;
				}
			}
		}
		
	}while(t!=w);
	printf("%d\n",Ans);
	return 0;
}

C Game Map

题意:给出一张无向图,每次只能从度数少的点走向严格度数更多的点,求一条最长路径的长度。

题解:条件限制下其实是一张有向图,且没有环,最长路直接拓扑排序就行了。

#include<bits/stdc++.h>
#define MAXN 100000+1109
#define MAXM 300000+1109

using namespace std;

int n,m;
int DU[MAXN],Du[MAXN],RD[MAXN];
int L[MAXM],R[MAXM];
vector <int> f[MAXN];
int dl[MAXN];
int Ans[MAXN];

int main()
{
	scanf("%d%d",&n,&m);
	int x,y;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&y);
		x++;y++;
		L[i]=x;R[i]=y;
		DU[x]++;DU[y]++;
	}
	for(int i=1;i<=m;i++)
	{
		x=L[i];y=R[i];
		if(DU[x]<DU[y])
		{
			Du[x]++;RD[y]++;
			f[x].push_back(y);
		}
		if(DU[y]<DU[x])
		{
			Du[y]++;RD[x]++;
			f[y].push_back(x);
		}
	}
	
	int t=0,w=0;
	for(int i=1;i<=n;i++)
		if(RD[i]==0)
			dl[++w]=i,Ans[i]=1;
	do
	{
		x=dl[++t];
		for(int i=0;i<Du[x];i++)
		{
			y=f[x][i];
			Ans[y]=max(Ans[y],Ans[x]+1);
			RD[y]--;
			if(RD[y]==0)
			{
				dl[++w]=y;
			}
		}
	}while(t!=w);
	int Re=-1;
	for(int i=1;i<=n;i++)
		/*printf("%d\n",Ans[i]),*/Re=max(Re,Ans[i]);
	printf("%d",Re);
	return 0;
}

D Happy Number

题意:定义函数f(n)为n各个位数上数字平方的和。例如f(19)=1^2+9^2=82。若反复应用f()函数后,一个数字变成了1,那么这是一个happy number,如果进入了循环,则是一个unhappy number,判断一个数字是什么种类的。

题解:经过一次f()后,数字会很小,直接上暴力就行了。

#include<cstdio>
#define MAXN 1109

using namespace std;

int n;
bool b[MAXN];

bool Find(int x)
{
	if(x==1) return true;
	if(b[x]) return false;
	b[x]=true;
	int X=x,Sum=0;
	while(X)
	{
		Sum+=(X%10)*(X%10);
		X/=10;
	}
	return Find(Sum);
}

int main()
{
	scanf("%d",&n);
	int x=0;
	while(n)
	{
		x+=(n%10)*(n%10);
		n/=10;
	}
	if(Find(x)) printf("HAPPY\n");
	else printf("UNHAPPY\n");
	return 0;
}

E How Many to Be Happy?

题意:给出一张无向图,对于一条边e,h(e)的函数值为至少去掉多少条边,使得e是一种最小生成树方案中的一边。求所有h(e)的和。

题解:n<=100,用网络流来做。最小生成树是从小到大加边,如果当前边不在一个联通块上,那么加入这条边。那么只要保证长度严格小于e的所有边不能构成树且边e可以联通两个块就行了。将所有长度严格小于e的边建立一个网络流模型,流量均为1,求一个e两个端点之间的最小割即可。这样就可以用最小的花费将e的两个端点割开,保证e能够在最小生成树时被加入。

#include<bits/stdc++.h>
#define MAXN 110
#define MAXM 500+30

using namespace std;

struct Data
{
	int x,y,z;
}E[MAXM];


bool cmp(const Data &A,const Data &B)
{
	return A.z<B.z;
}

int m,n,K,Du[MAXN],dis[MAXN],dl[2333333];
vector <int> f[MAXN],FB[MAXN],Flow[MAXN];
int cur[MAXN];
int Ans;

void ADD(int x,int y,int z)
{
	Du[x]++;Du[y]++;
	f[x].push_back(y);Flow[x].push_back(z);FB[x].push_back(Du[y]-1);
	f[y].push_back(x);Flow[y].push_back(0);FB[y].push_back(Du[x]-1);
}
bool BFS(int Begin,int End)
{
	int t=0,w=1,x,X;
	memset(dis,0xff,sizeof(dis));
	dis[Begin]=0;dl[1]=Begin;
	do
	{
		x=dl[++t];
		for(int i=0;i<Du[x];i++)
		{
			X=f[x][i];
			if(Flow[x][i]<=0||dis[X]>=0) continue;//不连通或已访问 
			dis[X]=dis[x]+1;dl[++w]=X;
		}
	}while(t<w);
	if(dis[End]>0) return true;
	else return false;
}
int Find(int x,int MFLOW,int y)
{
	if(x==y) return MFLOW;
	int X,h; 
	for(int i=cur[x];i<Du[x];i++)
	{
		cur[x]=i;
		X=f[x][i];
		if(Flow[x][i]>0&&dis[x]+1==dis[X]&&(h=Find(X,min(MFLOW,Flow[x][i]),y)))
		{
			Flow[x][i]-=h;
			Flow[X][FB[x][i]]+=h;
			return h;
		}
	}
	return 0;
}
int Solve(int x,int y)
{
	int X,Ans=0;
	while(1)
	{
		if(!BFS(x,y)) break;
		memset(cur,0,sizeof(cur));
		while((X=Find(x,0x7fffffff,y)))
			Ans+=X;
	}
		
	return Ans;
}


int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
		scanf("%d%d%d",&E[i].x,&E[i].y,&E[i].z);
	sort(E+1,E+1+m,cmp);
	for(int I=1;I<=m;I++)
	{
		for(int i=1;i<=n;i++)
		{
			Du[i]=0;
			f[i].clear();FB[i].clear();Flow[i].clear();
		}
		for(int i=1;i<I;i++)
			if(E[i].z<E[I].z)
				ADD(E[i].x,E[i].y,1),ADD(E[i].y,E[i].x,1);
		Ans+=Solve(E[I].x,E[I].y);
	}
	printf("%d",Ans);
	return 0;
}

F Philosopher’s Walk

题意:给出一个哲学家在花园散步的路径规律,这个规律比较符合分形的思想。给出这个花园的规模和哲学家走了多久,求出这个哲学家当前的坐标。

题解:递归求解即可,具体从题面中提取的操作见代码。

#include<bits/stdc++.h>

using namespace std;

int n,K;
int X,Y;

int L[30]={0,0,1,1};
int R[30]={0,1,1,0};

void Find(int x,int y)
{
	if(x==1) return ;
	int Num=(y-1)/(x*x/4);
	
	X+=L[Num]*(x/2);
	Y+=R[Num]*(x/2);
	
	if(Num==0) swap(L[1],L[3]),swap(R[1],R[3]);
	if(Num==3) swap(L[0],L[2]),swap(R[0],R[2]);
	Find(x/2,y-Num*(x*x/4));
}

int main()
{
	scanf("%d%d",&n,&K);
	Find(n,K);
	printf("%d %d",X+1,Y+1);
	return 0;
}

I Slot Machines

题意:给出一个数字序列,除去前k个数字后,剩下的数列是一个循环节长度为p的循环序列。有多个k和p都满足这个序列,求最小的k+p,若有相同,则取最小的p。

题解:反向KMP。将数列倒过来,枚举到一位数字时,认为这数字后面的是那前k位,后面的循环节长度就是i-next[i]。

#include<bits/stdc++.h>
#define MAXN 1000000+1109

using namespace std; 

int K,P,Ans=0x7fffffff;
int plen;
int p[MAXN];

int f[MAXN];//失配函数
void getFail() {
	//int plen = p.length();
	f[0] = 0; f[1] = 0;
	for (int i = 1; i < plen; i++) {
		int j = f[i];
		while (j && p[i] != p[j])  j = f[j];
		f[i + 1] = (p[i] == p[j]) ? j + 1 : 0;
	}
}

int main()
{
	scanf("%d",&plen);
	for(int i=plen-1;i>=0;i--)
		scanf("%d",&p[i]);
	getFail();
	int k,p;
	for(int i=1;i<=plen;i++)
	{
		k=i-f[i];
		p=plen-i;
		if(k+p>Ans) continue;
		if(k+p==Ans)
		{
			if(k>=K) continue;
			K=k;P=p;
		}
		else
		{
			Ans=k+p;
			K=k;P=p;
		}
		
	}
	printf("%d %d",P,K);
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值