把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【思维·状压】 jzoj1434灌水(COCI2009) 纪中集训提高B组

Time Limits: 1000 ms Memory Limits: 65536 KB Detailed Limits

Description

学生都很喜欢灌水,第一天只有Alice给她的每个朋友灌了一次水,从第二天开始,所有学生(包括Alice)将会有规律地去灌水:
  •如果前一天被灌了奇数次的水,他们将会给每个朋友灌一次水;
  •如果前一天被灌了偶数次的水,他们将会给每个朋友灌两次水。
  学生编号为1到N,Alice为1号,学生之间的朋友关系会给出。
  计算H天后一共灌了几次水。

Input

输入一行包含两个整数N和H(1<=N<=20,1<=H<=10^9),表示学生数和天数。
  接下来N行,每行包含N个‘0’或‘1’,(A,B)处的字符表示A和B的关系,‘1’表示是朋友关系,‘0’表示不是。注意自己和自己不是朋友关系,输入保证该矩阵是对称的。

Output

输出H天后一共灌水的数量。

Sample Input

输入1:
4 1
0110
1001
1001
0110

输入2:
4 2
0110
1001
1001
0110

输入3:
5 3
01000
10110
01000
01001
00010

Sample Output

输出1:
2

输出2:
14

输出3:
26

Hint

【样例解释】
  样例2中,第一天Alice灌了2次水,第二天学生1和学生4给学生2和学生3都灌了2次水,而学生2和学生3给学生1和学生4各灌水1次,2天一共灌了12次水。
【数据范围】
  50%的数据 H<=1000。


考场上时间紧张,没有多想就打了50分的暴力。
结果真的时间紧张,也没有时间想正解打对拍。
正解还是挺难想的吧,主要是天数太多了,刚开始以为是图结果跟图没有任何关系。

切入正题。
每个人去给别人灌水的次数只取决于上一天自己被灌水次数的奇偶性。
也就是说,我们只需要知道每个点是两种状态中的哪一种状态,而注意到 n n n只有20,想到状压 (但不是dp)
将奇点设为1,偶点设为0,就可以表示出所有的状态。发现状态只有 2 20 2^{20} 220个,也就是 1048576 1048576 1048576,比较小,而天数则高达 1 e 9 1e9 1e9,说明状态一定是循环的,找出循环节即可。

思路还是比较简单,但是实现有一点麻烦。参考了一下标程,自己又背着打了一遍。

#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define H 2000000
#define N 25
#define ll long long
int n,h;
char s[N][N];
ll f[H]/*标记当前状态出现在第几天*/,w[H]/*记录这个状态下的从第一天起总的ans*/;
ll g[N][N]/*邻接表*/,r[N];
int main()
{
	scanf("%d %d",&n,&h);
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s[i]+1);
		for(int j=1;j<=n;j++)
			if(s[i][j]=='1')
				g[i][++g[i][0]]=j;
	}
	r[1]=1;//二进制状压预处理 第i个人对应第i位为1 
	for(int i=2;i<=n;i++)
		r[i]=r[i-1]*2;
	ll t=0,pre=0;//t是当前状态,pre是上一天的状态
	for(int i=1;i<=g[1][0];i++)
		t+=r[g[1][i]];
	ll res=g[1][0],ans=0;
	w[t]=res,f[t]=1;
	bool flag=0;//标记是否出现循环 有可能天数太短不足以出现循环 
	for(int d=2;d<=h;d++)
	{
		pre=t,t=0;
		for(int i=1;i<=n;i++)
		{
			if(pre&r[i])//上一天被灌水奇数次
			{
				for(int j=1;j<=g[i][0];j++)
					if(t&r[g[i][j]])//之前已有奇数次
						t-=r[g[i][j]];
					else t+=r[g[i][j]];
				res+=g[i][0];
			}
			else res+=2*g[i][0];//灌水偶数次对状态没有影响 
		}
		if(!f[t])
			f[t]=d,w[t]=res;
		else//出现循环 
		{
			ans=w[t]+(h-f[t])/*剩多少天*//(d-f[t])/*循环节长度*/*(res-w[t])/*每个周期的值*/;
			h=(h-f[t])%(d-f[t]);//不能整除的天数 
			flag=1;
			break;
		}
	}
	if(flag)
	{//还剩下的不能整除的天数就再暴力找一遍 
		res=0;
		for(int d=1;d<=h;d++)
		{
			pre=t,t=0;
			for(int i=1;i<=n;i++)
			{
				if(pre&r[i])
				{
					for(int j=1;j<=g[i][0];j++)
						if(t&r[g[i][j]])
							t-=r[g[i][j]];
						else t+=r[g[i][j]];
					res+=g[i][0];
				}
				else res+=2*g[i][0];
			}
		}
	}
	printf("%lld\n",ans+res);
	return 0;
} 
posted @ 2019-08-06 19:41  Starlight_Glimmer  阅读(10)  评论(0编辑  收藏  举报  来源
浏览器标题切换
浏览器标题切换end