0905膜你赛测试
洛谷P3942 将军令
题目描述
又想起了四月。
如果不是省选,大家大概不会这么轻易地分道扬镳吧? 只见一个又一个昔日的队友离开了机房。
凭君莫话封侯事,一将功成万骨枯。
梦里,小 F 成了一个给将军送密信的信使。
现在,有两封关乎国家生死的密信需要送到前线大将军帐下,路途凶险,时间紧迫。小 F 不因为自己的祸福而避趋之,勇敢地承担了这个任务。
不过,小 F 实在是太粗心了,他一不小心把两封密信中的一封给弄掉了。
小 F 偷偷打开了剩下的那封密信。他 发现一副十分详细的地图,以及几句批文——原来 这是战场周围的情报地图。他仔细看后发现,在这张地图上标记了 n 个从 1 到 n 标号的 驿站,n − 1 条长度为 1 里的小道,每条小道双向连接两个不同的驿站,并且驿站之间可以 通过小道两两可达。
小 F 仔细辨认着上面的批注,突然明白了丢失的信的内容了。原来,每个驿站都可以驻 扎一个小队,每个小队可以控制距离不超过 k 里的驿站。如果有驿站没被控制,就容易产 生危险——因此这种情况应该完全避免。而那封丢失的密信里,就装着朝廷数学重臣留下的 精妙的排布方案,也就是用了最少的小队来控制所有驿站。
小 F 知道,如果能计算出最优方案的话,也许他就能够将功赎过,免于死罪。他找到了你,你能帮帮他吗?当然,小F在等待你的支援的过程中,也许已经从图上观察出了一些可能会比较有用的 性质,他会通过一种特殊的方式告诉你。
输入格式
从标准输入中读入数据。
输入第 1 行一个正整数 n,k,t,代表驿站数,一支小队能够控制的最远距离,以及特 殊性质所代表的编号。关于特殊性质请参照数据范围。
输入第 2 行至第 n 行,每行两个正整数 ui,vi,表示在ui和 vi
间,有一条长度为 一里的小道。
输出格式
输出到标准输出中。
输出一行,为最优方案下需要的小队数。
输入输出样例
输入1
4 1 0
1 2
1 3
1 4
输出1
1
输入1
6 1 0
1 2
1 3
1 4
4 5
4 6
输出2
2
解析
贪心,每次选深度最小的点看它是否被控制,若没有就向上走k个点放置一个小队,此时放的一定是有必要而且影响值范围最大。
然后考虑如何反应一个点的影响。对于一个小队,设u是到小队距离为a(a<k)的点,那么到u距离小于等于k-a的点肯定也被控制了。
那么我们给一个点附上一个权值a,表示方圆与它距离为a的点都会被控制。
当我们在一个点放置小队时,这个点的权值赋为k,它父亲的权值赋值为k-1,父亲的父亲的权值赋值为k-2。。。。。。
当我们判断一个点是否被控制是,向上找k个父亲,看是否存在 权值大于等于到这个点距离 的点就好了
代码如下
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100005;
int n,k,t,ans,cnt;
int v[maxn*2],nx[maxn*2],info[maxn];
int q[maxn],fa[maxn],dep[maxn],vis[maxn];
bool cmp(int a,int b){return dep[a]>dep[b];}
void add(int u1,int v1){nx[++cnt]=info[u1];info[u1]=cnt;v[cnt]=v1;}
void dfs(int x,int f){dep[x]=dep[fa[x]=f]+1;for(int i=info[x];i;i=nx[i])if(v[i]!=f)dfs(v[i],x);}
int main()
{
scanf("%d%d%d",&n,&k,&t);q[n]=n;vis[n]=-1;
for(int i=1,u1,v1;i<n;i++)scanf("%d%d",&u1,&v1),add(u1,v1),add(v1,u1),q[i]=i,vis[i]=-1;
dfs(1,0);sort(q+1,q+1+n,cmp);
for(int i=1;i<=n;i++)
{
int flag=(vis[q[i]]>=0),an=q[i];
for(int j=1;j<=k&&fa[an];j++)an=fa[an],flag|=(vis[an]>=j);
if(flag)continue;vis[an]=k;ans++;
for(int j=1;j<=k&&fa[an];j++)an=fa[an],vis[an]=max(vis[an],k-j);
}
printf("%d\n",ans);
}
洛谷P3941 入阵曲
题目描述
小 F 很喜欢数学,但是到了高中以后数学总是考不好。
有一天,他在数学课上发起了呆;他想起了过去的一年。一年前,当他初识算法竞赛的 时候,觉得整个世界都焕然一新。这世界上怎么会有这么多奇妙的东西?曾经自己觉得难以 解决的问题,被一个又一个算法轻松解决。
小 F 当时暗自觉得,与自己的幼稚相比起来,还有好多要学习的呢。
一年过去了,想想都还有点恍惚。
他至今还能记得,某天晚上听着入阵曲,激动地睡不着觉,写题写到鸡鸣时分都兴奋不 已。也许,这就是热血吧。
也就是在那个时候,小 F 学会了矩阵乘法。让两个矩阵乘几次就能算出斐波那契数列的 第 10^100项,真是奇妙无比呢。
不过,小 F 现在可不想手算矩阵乘法——他觉得好麻烦。取而代之的,是一个简单的小 问题。他写写画画,画出了一个
n×m的矩阵,每个格子里都有一个不超过 k的正整数。
小 F 想问问你,这个矩阵里有多少个不同的子矩形中的数字之和是k 的倍数? 如果把一个子矩形用它的左上角和右下角描述为
(x1,y1,x2,y2),其中x1≤x2,y1≤y2; 那么,我们认为两个子矩形是不同的,当且仅当他们以
(x1,y1,x2,y2)表示时不同;也就是 说,只要两个矩形以
(x1,y1,x2,y2)表示时相同,就认为这两个矩形是同一个矩形,你应该 在你的答案里只算一次。
输入格式
从标准输入中读入数据。
输入第一行,包含三个正整数 n,m,k。
输入接下来 n 行,每行包含m个正整数,第i行第j列表示矩阵中第i行第j列中所填的正整数 aij。
输出格式
输出到标准输出中。
输入一行一个非负整数,表示你的答案。
输入输出样例
输入 #1
2 3 2
1 2 1
2 1 2
输出 #1
6
解析
很容易想到枚举左上右下两个端点,但这样肯定超时。
于是试着想了一下中途相遇法,枚举左上右上两个端点然后储存它们的信息,之后枚举左下右下两个端点时直接处理就好。
然后。。。。。。发现可以枚举矩阵的左边界与右边界,然后将中间每一行的值加起来缩成一个点。然后再求一个前缀和,模k相同余数的就可以组合到一起,对于每个余数,贡献就是 个数*(个数-1)/2,另外注意余数为0的情况。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=100005;
int n,k,t,ans,cnt;
int v[maxn*2],nx[maxn*2],info[maxn];
int q[maxn],fa[maxn],dep[maxn],vis[maxn];
bool cmp(int a,int b){return dep[a]>dep[b];}
void add(int u1,int v1){nx[++cnt]=info[u1];info[u1]=cnt;v[cnt]=v1;}
void dfs(int x,int f){dep[x]=dep[fa[x]=f]+1;for(int i=info[x];i;i=nx[i])if(v[i]!=f)dfs(v[i],x);}
int main()
{
scanf("%d%d%d",&n,&k,&t);q[n]=n;vis[n]=-1;
for(int i=1,u1,v1;i<n;i++)scanf("%d%d",&u1,&v1),add(u1,v1),add(v1,u1),q[i]=i,vis[i]=-1;
dfs(1,0);sort(q+1,q+1+n,cmp);
for(int i=1;i<=n;i++)
{
int flag=(vis[q[i]]>=0),an=q[i];
for(int j=1;j<=k&&fa[an];j++)an=fa[an],flag|=(vis[an]>=j);
if(flag)continue;vis[an]=k;ans++;
for(int j=1;j<=k&&fa[an];j++)an=fa[an],vis[an]=max(vis[an],k-j);
}
printf("%d\n",ans);
}
洛谷P3943 星空
题目描述
逃不掉的那一天还是来了,小 F 看着夜空发呆。
天上空荡荡的,没有一颗星星——大概是因为天上吹不散的乌云吧。
心里吹不散的乌云,就让它在那里吧,反正也没有机会去改变什么了。
小 C 拿来了一长串星型小灯泡,假装是星星,递给小 F,想让小 F 开心一点。不过,有 着强迫症的小 F 发现,这串一共 n 个灯泡的灯泡串上有 k 个灯泡没有被点亮。小 F 决定 和小 C 一起把这个灯泡串全部点亮。
不过,也许是因为过于笨拙,小 F 只能将其中连续一段的灯泡状态给翻转——点亮暗灯 泡,熄灭亮灯泡。经过摸索,小 F 发现他一共能够翻转 m 种长度的灯泡段中灯泡的状态。
小 C 和小 F 最终花了很长很长很长很长很长很长的时间把所有灯泡给全部点亮了。他 们想知道他们是不是蠢了,因此他们找到了你,让你帮忙算算:在最优的情况下,至少需要 几次操作才能把整个灯泡串给点亮?
输入格式
从标准输入中读入数据。
输入第 1 行三个正整数 n,k,m。
输入第 2 行k个正整数,第i个数表示第 i 个被没点亮的灯泡的位置 ai。
输入第 3 行m个正整数,第i个数表示第 i 种操作的长度bi。
保证所有bi互不相同;保证对于
1≤i<k1有ai<ai+1;保证输入数据有解。
输出格式
输出标准输入中。
输出一行一个非负整数,表示最少操作次数。
输入输出样例
输入 #1
5 2 2
1 5
3 4
输出 #1
2
解析
区间反转实现起来有一些困难,所以考虑转换。
发现只要把原来的01状态数组转化成前缀亦或和数组,就可以再O(1)的时间复杂度内实现翻转,
比如反转原数组的l到l+r,就相当于原数组l到l+r-1都要亦或1
新数组中只需要l和r+1亦或1就可以了,即每次翻转可转化为亦或l与l+len。
若l,l+len都为1,则减少两个1
若其中一个为1,则相当于交换1的位置。
现在要把所有1都消掉,是不是有点像图论?。
因为k很小,所以可以先用bfs求出任意两个1消掉的最小代价,然后直接状压dp
代码
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;queue<int>q;
int n,m,cnt,I,K,d[40005],f[2000000],bit[40005],len[65],pos[25],w[25][25];
void bfs(int x)
{
memset(d,0x3f,sizeof d);q.push(pos[x]);d[pos[x]]=0;
while(!q.empty())
{
int nw=q.front();q.pop();
for(int i=1;i<=m;i++)
{
if(nw+len[i]<=n+1&&d[nw+len[i]]==inf)d[nw+len[i]]=d[nw]+1,q.push(nw+len[i]);
if(nw-len[i]>=1&&d[nw-len[i]]==inf)d[nw-len[i]]=d[nw]+1,q.push(nw-len[i]);
}
}
for(int i=1;i<=cnt;i++)if(d[pos[i]]!=inf)w[x][i]=d[pos[i]];
}
int main()
{
scanf("%d%d%d",&n,&K,&m);
for(int i=1,x;i<=K;i++)scanf("%d",&x),bit[x]^=1,bit[x+1]^=1;
for(int i=1;i<=m;i++)scanf("%d",&len[i]);
for(int i=1;i<=n+1;i++)if(bit[i])pos[++cnt]=i;
memset(w,0x3f,sizeof w);memset(f,0x3f,sizeof f);
for(int i=1;i<=cnt;i++)bfs(i);
f[0]=0;I=(1<<cnt)-1;
for(int s=1;s<=I;s++)
for(int i=0;(1<<i)<=s;i++)
for(int j=i+1;(1<<j)<=s&&(s&(1<<i));j++)
{
if(!(s&(1<<j)))continue;
f[s]=min(f[s],f[(s^(1<<i))^(1<<j)]+w[i+1][j+1]);
}
printf("%d\n",f[I]);
}