【思维·状压】 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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现