自己的域名博客到期了,拷回来备份
自己的域名博客到期了,把上面写的28篇博客再拷回来放在这。
省赛总结
2015/05/20 | ACM算法学习 | bfshm | 暂无评论 | 1868 views
第六届省赛总结
首先说一下,青岛的空气很好,山科的校园真的不错,,然后是比赛的情况,热身赛的题目都不涉及什么算法,我们做了三道题,接下来的题比较麻烦,所以测了一下环境什么的。。。
第二天正式赛,我们吃过早饭,在赛场外照了合影,然后进入赛场,可能是因为大三狗的原因,心里比较平静。比赛开始了,一宁调试机器,会神从前面读题,我从后面读题,刚开始还算是比较顺利,一直做了4个题的时候还没有什么大的问题。这时过题最多的题是H题,然后我们就一起看了H题,我们三个人看了一会H题,有一些想法,但是也并没有想出来可以解决这个题的解法。这个时候,一宁说想写一下I题,虽然这个题过的人并不多,但是一宁说可以用字典树写,于是一宁去写I题。我则因为H题实在没有什么好的思路,就去看了这个时候过题也很多的L题,而会神依然在看H题,我中间拉着会神帮我确认了一下L题的题意,虽然还是读错了题。总之,中间有很长一段时间,一宁在敲I题,会神在想H题,而我觉得L题是可做的,一直在反复看L题。之后一宁交了一下I题,返回TLE,这时已经快封榜了,我们已经有两个多小时没有过题了,而有好多队伍5题及以上了,我们有些着急。这个时候我已经想清楚了L题,于是我说先搞一下L题吧,因为一宁的图论比较好,跟一宁说了一下,就是一个需要缩点的题,一宁找了一下模板,很快的敲了,然后返回AC的时候我们终于稍微松了一口气。随后,会神试了一下H题,返回WA,然后我打表计算了一下,发现会神的思路是错的。后来还交了一发I题,也没有过,就这样5题收场。
这样的结果并不能令人满意,我们自身还是存在一些问题的,卡在H题的时候,我们三个人都没能想出来好的解法,最后用一个错误的思路去做的时候,也没能很快的指出来错误。这样的结果,主要是我们赛前比较松懈造成的。
省赛结束之后,跟老师说了一下,我就投入到考研大军之中了。最近一直在看高数,才知道数学已经离我很遥远了,在做高数题的时候发现一个题目需要各种转化推导,有些题目还需要证明,这种感觉就和acm做题的时候是差不多的,其实acm很多情况下还是拼数学功底的。当然,如果以后还有机会也希望能在acm的路上多走一会。回顾一下自己的acm经历,问自己在acm上到底有什么收获,其实acm之于我,以正式的比赛来看,并没有取得什么好的成绩,但是真的很庆幸从一开始自己就能够选择acm,并一直坚持到现在。如果说acm对我的影响的话,有很多,做acm对自己的编程能力、思维能力、团队合作能力都有非常大的提高,也让我知道如何能够做好一件事,很享受自己做题的时候,经过长时间的思考,突然想出了可行的做法,然后迅速敲完代码AC的感觉,也很享受组队做题的时候,本来自己只有一点思路,却随着同队友的讨论,突然脑洞大开,想出正解时头脑风暴式的体验。
最近,也在坚持跑步锻炼身体,其实上大学以来,自己逐渐变得有些懈怠了,希望尽快能够找回那个奋斗的自己。因为自己复习的算是挺晚的了,进度比其他考研的人差了很大一截,希望可以通过自己不断的努力,在考研的赛场上达到自己的目标。集训队在老师的带领和队员们的努力下,成绩明显一年比一年好了,也希望集训队在即将到来的邀请赛和亚洲区域赛中有更好的成绩!
HDU 4089 Activation(概率dp)
2015/05/07 | ACM算法学习 | bfshm | 暂无评论 | 1968 views
题目链接
下面的题意和分析转自:http://www.cnblogs.com/kuangbin/archive/2012/10/03/2710987.html
题意:有n个人排队等着在官网上激活游戏。Tomato排在第m个。
对于队列中的第一个人。有一下情况:
1、激活失败,留在队列中等待下一次激活(概率为p1) 2、失去连接,出队列,然后排在队列的最后(概率为p2)
3、激活成功,离开队列(概率为p3) 4、服务器瘫痪,服务器停止激活,所有人都无法激活了。
求服务器瘫痪时Tomato在队列中的位置<=k的概率
解析:
概率DP;
设dp[i][j]表示i个人排队,Tomato排在第j个位置,达到目标状态的概率(j<=i), dp[n][m]就是所求
j==1: dp[i][1]=p1*dp[i][1]+p2*dp[i][i]+p4;
2<=j<=k: dp[i][j]=p1*dp[i][j]+p2*dp[i][j-1]+p3*dp[i-1][j-1]+p4;
k<j<=i: dp[i][j]=p1*dp[i][j]+p2*dp[i][j-1]+p3*dp[i-1][j-1];
化简:
j==1: dp[i][1]=p*dp[i][i]+p41;
2<=j<=k: dp[i][j]=p*dp[i][j-1]+p31*dp[i-1][j-1]+p41;
k<j<=i: dp[i][j]=p*dp[i][j-1]+p31*dp[i-1][j-1];
其中:
p21=p2/(1-p1); p31=p3/(1-p1) p41=p4/(1-p1)
可以循环i=1->n 递推求解dp[i].在求解dp[i]的时候dp[i-1]就相当于常数了。
在求解dp[i][1~i]时等到下列i个方程
j==1: dp[i][1]=p*dp[i][i]+c[1];
2<=j<=k:dp[i][j]=p*dp[i][j-1]+c[j];
k<j=i: dp[i][j]=p*dp[i][j]+c[j];
其中c[j]都是常数了。上述方程可以解出dp[i]了。
首先是迭代得到 dp[i][i].然后再代入就可以得到所有的dp[i]了。
注意特判一种情况。就是p4<eps时候,就不会崩溃了,应该直接输出0
解释一下如何求解i 个方程,以d[3][3]为例,首先列出来3个方程,然后相当于有三个方程三个未知数,把其他的方程带入
d[3][3]的方程可得: d33 = p21*{p21*(p21*d33 + p41) + c1} + c2; 然后可以解出来d33.
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 2e3 + 10;
const double eps = 1e-9;
double d[maxn][maxn], c[maxn], pp[maxn];
int n, m, k;
double p1, p2, p3, p4;
int main()
{
while(~scanf("%d%d%d%lf%lf%lf%lf", &n, &m, &k, &p1, &p2, &p3, &p4))
{
if(p4 < eps)
{
printf("0.00000n");
continue;
}
double p21 = p2/(1.0-p1);
double p31 = p3/(1.0-p1);
double p41 = p4/(1.0-p1);
pp[0] = 1.0;
for(int i = 1; i <= n; i++)
pp[i] = pp[i-1] * p21;
d[1][1] = p4/(1.0 - p1 - p2);
c[1] = p41;
for(int i = 2; i <= n; i++)
{
int num = min(k, i); // 不加这个会MLE
for(int j = 2; j <= num; j++)
c[j] = d[i-1][j-1]*p31 + p41;
for(int j = k+1; j <= i; j++)
c[j] = d[i-1][j-1] * p31;
double tmp = 0.0;
for(int j = i; j > 0; j--)
tmp += pp[i-j] * c[j];
d[i][i] = tmp/(1.0 - pp[i]);
d[i][1] = p21*d[i][i] + p41;
for(int j = 2; j < i; j++)
d[i][j] = p21*d[i][j-1] + c[j];
}
printf("%.5lfn", d[n][m]);
}
return 0;
}
hdu 5218 Game( dp )(赛码”BestCoder”杯中国大学生程序设计冠军赛 1005 Game)
2015/05/05 | ACM算法学习 | bfshm | 暂无评论 | 1859 views
题目链接
题意:
n个人站成一排,开始指向第一个人,每轮从集合中取一个数x然后然后往后x个人,并且把那个人删掉,最优剩下的人赢,问那些人可能赢。
分析:
先贴一下官方的题解,令Fi,j代表剩下i个人时,若BrotherK的位置是1,那么位置为j的人是否可能获胜
转移的时候可以枚举当前轮指定的数是什么,那么就可以计算出当前位置j的人在剩下i − 1个人时的位置(假设BrotherK所处的位置是1),然后利用之前计算出的F值判定此人是否可能获胜
dp[i][j],表示剩下i个人,并且现在指向1位置,对于j位置的人是否赢,这都是相对位置。转移时枚举i,j和集合中选取的数a[k]。
时间复杂度为O(n3)
看完题解后,感觉更晕了。。
仔细想了一下,这个题其实相当于一个倒着的递推,d[1][0]表示只剩一个人的时候,肯定是在位置0的人获胜,注意这里的位置都是相对位置,然后逆推,对每种情况a[k]进行逆推枚举 并 取余,就可以知道上一次这个存活的人的相对位置。
直到逆推到剩余N个人的时候,这时候还存活的就是 答案了。
C++
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define LL long long
const int maxn = 2e2 + 10;
using namespace std;
int a[maxn], d[maxn][maxn], ans[maxn];
int main()
{
int i, j, k, t, n, m, cnt;
scanf("%d", &t);
while(t--)
{
cnt = 0;
memset(d, 0, sizeof(d));
scanf("%d%d", &n, &m);
for(i = 0; i < m; i++)
scanf("%d", &a[i]);
d[1][0] = 1;
for(i = 2; i <= n; i++)
for(j = 0; j < n; j++)
if(d[i-1][j])
for(k = 0; k < m; k++)
d[i][(j+a[k]) % i] = 1;
for(i = 0; i < n; i++)
if(d[n][i])
ans[cnt++] = i+1;
cout<<cnt<<endl;
for(i = 0; i < cnt; i++)
{
if(i == cnt-1)
printf("%dn", ans[i]);
else
printf("%d ", ans[i]);
}
}
return 0;
}
NYOJ 石子合并(一)(四边形不等式优化 区间dp)
2015/05/04 | ACM算法学习 | bfshm | 暂无评论 | 2102 views
题目链接
石子合并(一)
时间限制:1000 ms | 内存限制:65535 KB
难度:3
描述
有N堆石子排成一排,每堆石子有一定的数量。现要将N堆石子并成为一堆。合并的过程只能每次将相邻的两堆石子堆成一堆,每次合并花费的代价为这两堆石子的和,经过N-1次合并后成为一堆。求出总的代价最小值。
输入
有多组测试数据,输入到文件结束。
每组测试数据第一行有一个整数n,表示有n堆石子。
接下来的一行有n(0< n <200)个数,分别表示这n堆石子的数目,用空格隔开
输出
输出总代价的最小值,占单独的一行
样例输入
3
1 2 3
7
13 7 8 16 21 4 18
样例输出
9
239
分析:
普通的区间dp,d[i][j]代表 i 到 j 最小的代价,sum[i][j]代表从i 到 j 的代价花费。
没优化的代码:O(n^3)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <algorithm>
#define LL __int64
const int maxn = 2e2 + 10;
using namespace std;
int a[maxn], d[maxn][maxn], sum[maxn][maxn];
int main()
{
int n, i, j, k, l;
while(~scanf("%d", &n))
{
for(i = 0; i < n; i++)
scanf("%d", &a[i]);
memset(d, 0, sizeof(d));
for(i = 0; i < n; i++)
{
sum[i][i] = a[i];
for(j = i+1; j < n; j++)
sum[i][j] = sum[i][j-1] + a[j];
}
for(l = 1; l < n; l++)
{
for(i = 0; i+l < n; i++)
{
j = i + l;
d[i][j] = d[i][j-1] + sum[i][j];
for(k = i; k+1 <= j; k++)
{
d[i][j] = min(d[i][j], d[i][k]+d[k+1][j]+sum[i][j]);
}
}
}
printf("%dn", d[0][n-1]);
}
return 0;
}
首先贴一点四边形不等式的理论: 下面引用自:http://www.cnblogs.com/zxndgv/archive/2011/08/02/2125242.html
黑书上对于四边形不等式的讲解性价比还是比较高的,讲的都是重点,很好。
引用一下:
当函数w(i,j)满足 w(a,c)+w(b,d) <= w(b,c)+w(a,d) 且a<=b< c <=d 时,我们称w(i,j)满足四边形不等式。。
当函数w(i, j)满足w(i’, j) <= w(i, j’); i <= i’ < j <= j’ 时,称w关于关于区间包含关系单 调。
s(i, j)=k是指m(i, j)这个状态的最优决策
以上定理的证明自己去查些资料
今天看得lrj的书中介绍的 四边形优化 做个笔记,加强理解
最有代价用d[i,j]表示
d[i,j]=min{d[i,k-1]+d[k+1,j]}+w[i,j]
其中w[i,j]=sum[i,j]
四边形不等式
w[a,c]+w[b,d]<=w[b,c]+w[a,d](a<b<c<d) 就称其满足凸四边形不等式
决策单调性
w[i,j]<=w[i’,j’] ([i,j]属于[i’,j’]) 既 i'<=i<j<=j’
于是有以下三个定理
定理一: 如果w同时满足四边形不等式 和 决策单调性 ,则d也满足四边形不等式
定理二:当定理一的条件满足时,让d[i,j]取最小值的k为K[i,j],则K[i,j-1]<=K[i,j]<=K[i+1,j]
定理三:w为凸当且仅当w[i,j]+w[i+1,j+1]<=w[i+1,j]+w[i,j+1]
由定理三知 判断w是否为凸即判断 w[i,j+1]-w[i,j]的值随着i的增加是否递减
于是求K值的时候K[i,j]只和K[i+1,j] 和 K[i,j-1]有关,所以 可以以i-j递增为顺序递推各个状态值最终求得结果 将O(n^3)转为O(n^2)
分析:
感觉这个四边形不等式优化 虽然理论有点高大上,但是还是挺好理解的,只是用s[i][j]记录i 到 j的最优决策,
因为s[i,j-1]<=s[i,j]<=s[i+1,j]的原因,所以可以把第三层循环极大的优化,变成O(n^2)。程序在OJ上的效率体现了这一点。
优化后的代码: 复杂度O(n^2),极大的提高了效率。
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <algorithm>
#define LL __int64
const int maxn = 2e2 + 10;
using namespace std;
int a[maxn], d[maxn][maxn], sum[maxn][maxn], s[maxn][maxn];
int main()
{
int n, i, j, k, l;
while(~scanf("%d", &n))
{
for(i = 0; i < n; i++)
scanf("%d", &a[i]);
memset(d, 0, sizeof(d));
for(i = 0; i < n; i++)
{
s[i][i] = i; //开始自己的分割点是自己
sum[i][i] = a[i];
for(j = i+1; j < n; j++)
sum[i][j] = sum[i][j-1] + a[j];
}
for(l = 1; l < n; l++)
{
for(i = 0; i+l < n; i++)
{
j = i + l;
d[i][j] = d[i][j-1] + sum[i][j];
for(k = s[i][j-1]; k <= s[i+1][j]; k++)
{ //因为满足四边形不等式的条件,所以s[i][j-1] <= s[i+1][j],而这个两个记录的各自区间的最优的分割点,所以可以以这两个为起始点。
if(d[i][k]+d[k+1][j]+sum[i][j] <= d[i][j])
s[i][j] = k; //更新最优的分割点
d[i][j] = min(d[i][j], d[i][k]+d[k+1][j]+sum[i][j]);
}
}
}
printf("%dn", d[0][n-1]);
}
return 0;
}
编程之美2015初赛第二场 hihoCoder 1159扑克牌 && 1160攻城略地(过大数据的解法)
2015/05/04 | ACM算法学习 | bfshm | 暂无评论 | 1036 views
题目链接
描述
一副不含王的扑克牌由52张牌组成,由红桃、黑桃、梅花、方块4组牌组成,每组13张不同的面值。现在给定52张牌中的若干张,请计算将它们排成一列,相邻的牌面值不同的方案数。
牌的表示方法为XY,其中X为面值,为2、3、4、5、6、7、8、9、T、J、Q、K、A中的一个。Y为花色,为S、H、D、C中的一个。如2S、2H、TD等。
输入
第一行为一个整数T,为数据组数。
之后每组数据占一行。这一行首先包含一个整数N,表示给定的牌的张数,接下来N个由空格分隔的字符串,每个字符串长度为2,表示一张牌。每组数据中的扑克牌各不相同。
输出
对于每组数据输出一行,形如”Case #X: Y”。X为数据组数,从1开始。Y为可能的方案数,由于答案可能很大,请输出模264之后的值。
数据范围
1 ≤ T ≤ 20000
小数据
1 ≤ N ≤ 5
大数据
1 ≤ N ≤ 52
样例输入
5
1 TC
2 TC TS
5 2C AD AC JC JH
4 AC KC QC JC
6 AC AD AS JC JD KD
样例输出
Case #1: 1
Case #2: 0
Case #3: 48
Case #4: 24
Case #5: 120
分析:
这个题即是有13个数,,每个数字最多有4个,相同的数字有不同的种类所以看成不一样,求相同数字不相邻的排列的方法数。
d[n1][n2][n3][n4][pre]表示有一个相同数字的个数,有两个相同数字的个数,有三个相同数字的个数,有四个相同数字的个数,pre表示之前用的几个相同数字。 然后标记搜索,因为结果和具体的数字没有关系,只是需要记录个数即可,所有只需要计算一遍d【】【】【】【】【】数组,每次输入也只需要统计一下个数就行。虽然相同数字是不同类型的,但是在搜索的时候把相同数字看成是一样的一个,最后再乘全排列, 搜索是如果当前的个数 和 之前放的是一样的,则减去一种可能性。
然后因为相同数字有不同的种类,所以还需要乘以全排列。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define LL unsigned long long //因为这题要求对2^64取余,用无符号的long long相当于对2^64取余
const int maxn = 3e2 + 10;
const LL mo = 1LL<<64; //虽然这里没用,但是也要注意如果数字是LL的话,首先需要把1转化成LL
using namespace std;
LL num[maxn], d[14][14][14][14][5];
LL dfs(int n1, int n2, int n3, int n4, int pre)
{
LL ret = 0;
if(n1 + n2 + n3 + n4 == 0) return 1;
if(d[n1][n2][n3][n4][pre] != -1)
return d[n1][n2][n3][n4][pre];
if(n1 > 0) ret += (n1-(pre==1)) * dfs(n1-1, n2, n3, n4, 0);
if(n2 > 0) ret += (n2-(pre==2)) * dfs(n1+1, n2-1, n3, n4, 1);
if(n3 > 0) ret += (n3-(pre==3)) * dfs(n1, n2+1, n3-1, n4, 2);
if(n4 > 0) ret += (n4-(pre==4)) * dfs(n1, n2, n3+1, n4-1, 3);
d[n1][n2][n3][n4][pre] = ret;
return ret;
}
int main()
{
int t, ca = 1;
LL n, i, ans;
int n1, n2, n3, n4;
char s[5];
memset(d, -1, sizeof(d)); //要放在外面,因为没必要重复计算
scanf("%d", &t);
while(t--)
{
memset(num, 0, sizeof(num));
scanf("%llu", &n);
getchar();
for(i = 0; i < n; i++)
{
scanf("%s", s);
num[s[0]] ++;
}
n1 = n2 = n3 = n4 = 0;
for(i = 0; i < 256; i++)
{
if(num[i]==1) n1 ++;
if(num[i]==2) n2 ++;
if(num[i]==3) n3 ++;
if(num[i]==4) n4 ++;
}
ans = dfs(n1, n2, n3, n4, 0);
for(i = 0; i < 256; i ++)
{
if(num[i]==1) ans *= 1;
if(num[i]==2) ans *= 1*2;
if(num[i]==3) ans *= 1*2*3;
if(num[i]==4) ans *= 1*2*3*4; //对相同数字的全排列
}
printf("Case #%d: %llun", ca++, ans);
}
return 0;
}
#1160 : 攻城略地
题目链接
描述
A、B两国间发生战争了,B国要在最短时间内对A国发动攻击。已知A国共有n个城市(城市编号1, 2, …, n),城市间有一些道路相连。每座城市的防御力为w,直接攻下该城的代价是w。若该城市的相邻城市(有道路连接)中有一个已被占领,则攻下该城市的代价为0。
除了占领城市,B国还要摧毁A国的交通系统,因而他们需要破坏至少k条道路。由于道路损毁,攻下所有城市的代价相应会增加。假设B国可以任意选择要摧毁的道路,那么攻下所有城市的最小代价是多少?
输入
第一行一个整数T,表示数据组数,以下是T组数据。
每组数据第一行包含3个整数n, m, k。
第二行是n个整数,分别表示占领城市1, 2, …, n的代价w。
接下来m行每行两个数i, j,表示城市i与城市j间有一条道路。
输出
对于每组数据输出一行,格式为”Case #X: Y”。X表示数据编号(从1开始),Y为答案。
数据范围
1 ≤ T ≤ 30
k ≤ m
0 ≤ w ≤ 108
小数据
1 ≤ n ≤ 1000
0 ≤ m ≤ 5000
大数据
1 ≤ n ≤ 106
0 ≤ m ≤ 106
样例输入
2
4 4 2
6 5 3 4
1 2
1 3
2 3
2 4
4 4 4
6 5 3 4
1 2
1 3
2 3
2 4
样例输出
Case #1: 7
Case #2: 18
分析:
非常可惜,在比赛的时候没有AC, 比完赛把代码删了一点就A了。
首先用并查集判断连通性,对于连通的用其中最小的代价来替代整个的就可以了,因为还有k个的限制,所以把
其他的放入数组里,然后按照代价从小到大排序,一个一个的删掉,直到满足k个。
C++
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define LL long long
const int INF = 1<<29;
const int maxn = 1e6 + 10;
using namespace std;
LL w[maxn], bin[maxn], f[maxn], tt[maxn];
struct node
{
LL id, val;
} p[maxn];
bool cmp(node a, node b)
{
return a.val < b.val;
}
LL find(LL x)
{
LL r, i, j;
r = x;
while(r != bin[r])
r = bin[r];
i = x;
while(bin[i] != r)
{
j = bin[i];
bin[i] = r;
i = j;
}
return r;
}
int main()
{
int t;
LL i, n, m, k;
LL ans, ca = 1;
LL fx, fy, x, y, cnt;
scanf("%d", &t);
while(t--)
{
scanf("%lld%lld%lld", &n, &m ,&k);
for(i = 0; i <= n; i++)
bin[i] = i;
for(i = 1; i <= n; i++)
scanf("%lld", &w[i]);
for(i = 0; i < m; i++)
{
scanf("%lld%lld", &x, &y);
fx = find(x);
fy = find(y);
if(fx == fy)
k --;
else
{
if(w[fx] < w[fy])
bin[fy] = fx;
else
bin[fx] = fy;
}
}
memset(f, 0, sizeof(f));
ans = 0;
cnt = 0;
for(i = 1; i <= n; i++)
{
if(bin[i] == i)
ans += w[i];
else
{
p[cnt].id = i;
p[cnt].val = w[i];
cnt ++;
f[bin[i]] ++;
}
}
sort(p, p+cnt, cmp);
memset(tt, 0, sizeof(tt));
for(i = 0; i < cnt; i++)
{
if(k <= 0) break;
x = p[i].id;
y = p[i].val;
ans += y;
tt[i] = 1;
k --;
f[bin[x]] --;
}
printf("Case #%lld: %lldn", ca++, ans);
}
return 0;
}
关系并查集(poj 1182 食物链 && hdu 2818 Building Block)
2015/04/24 | ACM算法学习 | bfshm | 暂无评论 | 885 views
题目链接:http://poj.org/problem?id=1182
动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。
有人用两种说法对这N个动物所构成的食物链关系进行描述:
第一种说法是”1 X Y”,表示X和Y是同类。
第二种说法是”2 X Y”,表示X吃Y。
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。
1) 当前的话与前面的某些真的话冲突,就是假话;
2) 当前的话中X或Y比N大,就是假话;
3) 当前的话表示X吃X,就是假话。
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。
Input
第一行是两个整数N和K,以一个空格分隔。
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。
若D=1,则表示X和Y是同类。
若D=2,则表示X吃Y。
Output
只有一个整数,表示假话的数目。
分析:
首先这个题必须要单组输入,否则会出错,可能是因为输入数据有多的,导致多组输入会输出多余的数据。
分析转载自:http://blog.csdn.net/niushuai666/article/details/6981689
此处rel有三种取值(假设节点x的父节点为rootx,即p[x].pre=rootx):
p[x].rel=0 ……表示节点x与其父节点rootx的关系是:同类
p[x].rel=1 ……表示节点x与其父节点rootx的关系是:被根结点吃
p[x].rel=2 ……表示节点x与其父节点rootx的关系是:吃根结点
由上面可知:
x->y 偏移量0时 x和y同类
x->y 偏移量1时 x被y吃
x->y 偏移量2时 x吃y
有了这个假设,我们就可以在并查集中完成任意两个元素之间的关系转换了。
不妨继续假设,x的当前集合根节点rootx,y的当前集合根节点rooty,x->y的偏移值为d-1(题中给出的询问已知条件)
(1)如果rootx和rooty不相同,那么我们把rooty合并到rootx上,并且更新relation关系域的值(注意:p[i].relation表示i的根结点到i的偏移量!!!!(向量方向性一定不能搞错))
此时 rootx->rooty = rootx->x + x->y + y->rooty,这一步就是大牛独创的向量思维模式
上式进一步转化为:rootx->rooty = (relation[x]+d-1+3-relation[y])%3 = relation[rooty],(模3是保证偏移量取值始终在[0,2]间)
(2)如果rootx和rooty相同(即x和y在已经在一个集合中,不需要合并操作了,根结点相同),那么我们就验证x->y之间的偏移量是否与题中给出的d-1一致
此时 x->y = x->rootx + rootx->y
上式进一步转化为:x->y = (3-relation[x]+relation[y])%3,
若一致则为真,否则为假。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define LL long long
const int maxn = 5e4 + 10;
using namespace std;
struct node
{
int pre, rel; //分别表示该节点的父节点 和 该节点与根节点的关系
}p[maxn];
int find(int x)
{
if(x == p[x].pre)
return x;
int tmp = p[x].pre; //为了计算关系,需要先保存父节点
p[x].pre = find(tmp); //路径压缩
p[x].rel = (p[tmp].rel + p[x].rel + 3)%3; //因为压缩路径的方法最多能产生3层,
//所以这个也是相当于向量的运算,这个关系可以验证.rootx-> = rootx->tmp+tmp->x
return p[x].pre;
}
int main()
{
int n, k, i;
int x, y, d, fx, fy;
int ans;
scanf("%d%d", &n, &k);
ans = 0;
for(i = 1; i <= n; i++)
{
p[i].pre = i;
p[i].rel = 0;
}
while(k--)
{
scanf("%d%d%d", &d, &x, &y);
if(x>n || y>n)
{
ans ++;
continue;
}
if(d==2 && x==y)
{
ans ++;
continue;
}
fx = find(x); fy = find(y);
if(fx != fy)
{
p[fy].pre = fx;
p[fy].rel = (p[x].rel + d-1 - p[y].rel + 3)%3; //rootx->rooty = rootx->x + x->y + y->rooty
}
else
{
if(d==1 && p[x].rel != p[y].rel)
{
ans ++;
continue;
}
if(d==2 && ((-p[x].rel+p[y].rel+3)%3 != d-1)) //x->y = x->rootx + rootx->y, rootx==rooty
{
ans ++;
continue;
}
}
}
printf("%dn", ans);
return 0;
}
hdu 2818 Building Block
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2818
题意:
john 正在玩积木,有N个积木编号为1、、、N,分成N堆,每堆只包含一个积木,然后做P次操作,操作分为2种,
M X Y:把包含X的一堆放到包含Y的一堆上,如果XY同在一堆上,不做处理
C X:计算出X积木下边有多少个积木
每次遇到C操作,输出数量
分析:
under[i] 表示i 下面的积木数量,sum[i]表示以i为根的积木的个数。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define LL long long
const int maxn = 3e4 + 10;
using namespace std;
int sum[maxn], under[maxn], bin[maxn];
int find(int x)
{
int tmp;
if(x != bin[x])
{
tmp = find(bin[x]);
under[x] += under[bin[x]];
bin[x] = tmp;
}
return bin[x];
}
void merge(int x, int y)
{
int fx = find(x), fy = find(y);
if(fx != fy)
{
under[fx] = sum[fy];
sum[fy] += sum[fx];
bin[fx] = fy;
}
}
int main()
{
int i, t, x, y;
char ch;
while(~scanf("%d", &t))
{
for(i = 0; i <= maxn-5; i++)
{
under[i] = 0;
sum[i] = 1;
bin[i] = i;
}
while(t--)
{
getchar();
scanf("%c %d", &ch, &x);
if(ch=='M')
{
scanf("%d", &y);
merge(x, y);
}
else
{
find(x);
printf("%dn", under[x]);
}
}
}
return 0;
}
UVALive 6255 Kingdoms (状态搜索,位运算)
2015/04/23 | ACM算法学习 | bfshm | 暂无评论 | 1059 views
题目链接
Input
The first line of the input contains the number of test cases T. The descriptions of the test cases follow:
The description of each test case starts with a line containing the number of the kingdoms n,
1 ≤ n ≤ 20. Then n lines follow, each containing n space-separated numbers. The j-th number in the
i-th line is the number dij of gold coins that the i-th kingdom owes to the j-th one. You may assume
that dii = 0 and dij = −dji for every 1 ≤ i, j ≤ n. Also, |dij | ≤ 106
for all possible i, j.
Output
Print the answers to the test cases in the order in which they appear in the input. For each test
case, print a single line containing the indices of the kingdoms that can become the sole survivors, in
increasing order. If there are no such kingdoms, print a single number ‘0’.
Sample Input
1
3
0 -3 1
3 0 -2
-1 2 0
Sample Output
1 3
题意:n个国家,给出国家间相互的债务关系,每个国家如果债务>收入就要破产,破产后该国的所有债务关系全部清除,第一个破产的国家不同有可能造成最后的没破产的国家的不同,问哪些国家有可能成为独自存活的国家。
分析:
20个城市,用sta表示城市破产的状况,为1表示破产,0表示没破产,
标记状态,搜索一次的时间复杂度为O(1<<20);
每次dfs让一个国家破产,如果最后只剩下一个就是可以存活的国家,用位运算来查看当前国家是否破产。#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define LL __int64
const int INF = 1<<29;
const int maxn = 20 + 10;
using namespace std;
int c[maxn][maxn], ans[maxn], n, vis[(1<<20) + 10], cnt;
void dfs(int sta, int num)
{
vis[sta] = 1;
int i, j;
if(num == n-1)
{
for(i = 0; i < n; i++)
if((sta&(1<<i))==0)
ans[cnt++] = i;
return;
}
for(i = 0; i < n; i++)
if((sta&(1<<i))==0 && vis[sta|(1<<i)]==0)
{
int sum = 0;
for(j = 0; j < n; j++)
if((sta&(1<<j))==0)
sum -= c[i][j];
if(sum < 0)
dfs((sta|(1<<i)), num+1);
}
}
int main()
{
//freopen("1.txt", "r", stdin);
int t, i, j;
scanf("%d", &t);
while(t--)
{
memset(vis, 0, sizeof(vis));
scanf("%d", &n);
for(i = 0; i < n; i++)
for(j = 0; j < n; j++)
scanf("%d", &c[i][j]);
cnt = 0;
dfs(0, 0);
sort(ans, ans+cnt);
if(cnt==0)
cout<<cnt<<endl;
else
for(i = 0; i < cnt; i++)
{
if(i==cnt-1) cout<<ans[i]+1<<endl;
else printf("%d ", ans[i]+1);
}
}
return 0;
}
山东省第四届ACM省赛 sdut 2607 Mountain Subsequences(dp)
2015/04/10 | ACM算法学习 | bfshm | 暂无评论 | 1002 views
题目
Mountain Subsequences
Time Limit: 1000ms Memory limit: 65536K
题目描述
Coco is a beautiful ACMer girl living in a very beautiful mountain. There are many trees and flowers on the mountain, and there are many animals and birds also. Coco like the mountain so much that she now name some letter sequences as Mountain Subsequences.
A Mountain Subsequence is defined as following:
1. If the length of the subsequence is n, there should be a max value letter, and the subsequence should like this, a1 < …< ai < ai+1 < Amax > aj > aj+1 > … > an
2. It should have at least 3 elements, and in the left of the max value letter there should have at least one element, the same as in the right.
3. The value of the letter is the ASCII value.
Given a letter sequence, Coco wants to know how many Mountain Subsequences exist.
输入
Input contains multiple test cases.
For each case there is a number n (1<= n <= 100000) which means the length of the letter sequence in the first line, and the next line contains the letter sequence.
Please note that the letter sequence only contain lowercase letters.
输出
For each case please output the number of the mountain subsequences module 2012.
示例输入
4
abca
示例输出
4
提示
The 4 mountain subsequences are:
aba, aca, bca, abca
题意:
给你一个长度为n的字符串仅由小写英文字母组成。
满足a[i]<a[i+1]<……<a[j]>a[j+1]>……a[k]
的子串的个数,其实也就是统计所有满足以某一元素为中心左边递增,右边递减的子串的数目,要求该子串
最小长度为3,中心元素左右都至少有一个元素。
分析:
用d[ch]表示以ch为结尾的递增的子序列的个数。l[i]表示i 左边满足题意的子序列个数。
r[i]表示i 右边满足题意的子序列的个数。l[i]*r[i]即可。
求l[i] 即所有<a[i] 的个数和。
求d[ch] 即前面所有以ch结尾的个数和 加上 当前以ch结尾的个数和 加 1,这个1是指只有ch的情况。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define LL __int64
const int INF = 1<<29;
const int maxn = 1e5 + 10;
const int mo = 2012;
using namespace std;
char s[maxn];
int d[30], l[maxn], r[maxn], a[maxn];
int main()
{
int i, j, n, ans;
while(~scanf("%d", &n))
{
memset(d, 0, sizeof(d));
memset(l, 0, sizeof(l));
memset(r, 0, sizeof(r));
scanf("%s", s);
for(i = 0; i < n; i++)
a[i] = s[i]-'a';
for(i = 0; i < n; i++)
{
for(j = 0; j < a[i]; j++)
l[i] = (l[i]+d[j])%mo;
d[a[i]] = (d[a[i]] + l[i] + 1)%mo;
}
memset(d, 0, sizeof(d));
for(i = n-1; i >= 0; i--)
{
for(j = 0; j < a[i]; j++)
r[i] = (r[i] + d[j])%mo;
d[a[i]] = (d[a[i]] + r[i] + 1)%mo;
}
ans = 0;
for(i = 0; i < n; i++)
ans = (ans + l[i]*r[i])%mo;
printf("%dn", ans);
}
return 0;
}
hdu 5187 zhx’s contest(快速乘法)
2015/03/16 | ACM算法学习 | bfshm | 暂无评论 | 1088 views
题目链接
zhx认为一个漂亮的序列{ai}下列两个条件均需满足。
1:a1..ai是单调递减或者单调递增的。
2:ai..an是单调递减或者单调递增的。
他想你告诉他有多少种排列是漂亮的。
因为答案很大,所以只需要输出答案模p之后的值。
输入描述
多组数据(不多于1000组)。读到文件尾。
每组数据包含一行两个整数n和p。(1≤n,p≤1018)
输出描述
每组数据输出一行一个非负整数表示答案。
输入样例
2 233
3 5
输出样例
2
1
分析:
如果n=1,答案是1,否则答案是2n−2。
证明:ai肯定是最小的或者最大的。考虑另外的数,如果它们的位置定了的话,那么整个序列是唯一的。
那么ai是最小或者最大分别有2n−1种情况,而整个序列单调增或者单调减的情况被算了2次,所以要减2。
要注意的一点是因为p>231,所以要用快速乘法。用法与快速幂相同。如果直接乘会超过long long范围,从而wa掉。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 200 + 10;
using namespace std;
LL p;
LL mul(LL a, LL b) //快速乘法,a*b相当于b个a相加
{
LL ret = 0LL;
while(b)
{
if(b&1)
ret = (ret+a)%p;
a = (a+a)%p;
b >>= 1;
}
return ret%p;
}
LL pow(LL a, LL b)//快速幂,a^b
{
LL ret = 1LL;
while(b)
{
if(b&1)
ret = mul(ret, a);
a = mul(a, a);
b >>= 1;
}
return ret%p;
}
int main()
{
LL n;
while(~scanf("%I64d%I64d", &n, &p))
{
if(n==1)
{
cout<<n%p<<endl;
continue;
}
printf("%I64dn", (pow(2LL, n)-2+p)%p); //注意加p,再取余
}
return 0;
}
第一类Stirling数和第二类Stirling(斯特林数)
2015/03/10 | ACM算法学习 | bfshm | 暂无评论 | 1536 views
第一类Stirling数 s(p,k)
s(p,k)的一个的组合学解释是:将p个物体排成k个非空循环排列的方法数。
s(p,k)的递推公式: s(p,k)=(p-1)*s(p-1,k)+s(p-1,k-1) ,1<=k<=p-1
边界条件:s(p,0)=0 ,p>=1 s(p,p)=1 ,p>=0
递推关系的说明:
考虑第p个物品,p可以单独构成一个非空循环排列,这样前p-1种物品构成k-1个非空循环排列,方法数为s(p-1,k-1);
也可以前p-1种物品构成k个非空循环排列,而第p个物品插入第i个物品的左边,这有(p-1)*s(p-1,k)种方法。
第二类Stirling数 S(p,k)
S(p,k)的一个组合学解释是:将p个物体划分成k个非空的不可辨别的(可以理解为盒子没有编号)集合的方法数。
k!S(p,k)是把p个人分进k间有差别(如:被标有房号)的房间(无空房)的方法数。
S(p,k)的递推公式是:S(p,k)=k*S(p-1,k)+S(p-1,k-1) ,1<= k<=p-1
边界条件:S(p,p)=1 ,p>=0 S(p,0)=0 ,p>=1
递推关系的说明:
考虑第p个物品,p可以单独构成一个非空集合,此时前p-1个物品构成k-1个非空的不可辨别的集合,方法数为S(p-1,k-1);
也可以前p-1种物品构成k个非空的不可辨别的集合,第p个物品放入任意一个中,这样有k*S(p-1,k)种方法。
第一类斯特林数和第二类斯特林数有相同的初始条件,但递推关系不同。
第一类斯特林数的题目:http://acm.hdu.edu.cn/showproblem.php?pid=3625
题意:就是给你N个房间,然后每个房间1把钥匙,你最初手里没有任何钥匙,要靠破门而入!这里只有第一个房间不能破门进去,其他都可以,给你房间数N,和最多能破门的个数K,让你求能全部把房间打开的概率!
分析:钥匙与门的对应关系呈现出环。打开一个门之后,环内的所有房间都可以进入。也就是说N个房间形成1–K个环的可能有多大。N个房间N个钥匙的总数为N!。
之后是求N个房间形成i个环的总数。
题目还有个特殊要求,不能破1号的门。
也就是说1号不能独立成环,否则就失败。
第一类斯特林数S(P,K)=(P-1)*S(P-1,K)+S(P-1,K-1)表示的正是N个元素形个K个非空循环排列的方法数。
枚举形成的环,但是要除掉1号独立成环的可能。
S(N,M)-S(N-1,M-1),N个元素形成 M个环,减去除了1之外的N-1个元素形成M-1个环,也就是1独立成环。
#include <iostream>
#include <stdio.h>
using namespace std;
const int A = 21;
long long ans[A][A],f[A];
int main()
{
int t,n,k;
ans[1][1] = 1;
f[0] = f[1] = 1;
for (int i = 2;i < A;i++)
{
for (int j = 1;j <= i;j++)
ans[i][j] = ans[i-1][j-1] + (i-1)*ans[i-1][j];
f[i] = f[i-1]*i;
}
cin >> t;
while (t-- && cin >> n >> k)
{
long long sum = 0;
for (int i = 1;i <= k;i++)
sum += ans[n][i] - ans[n-1][i-1];
printf("%.4lfn",(double)sum/f[n]);
}
return 0;
} 第二类Stirling数的题目:http://acm.sdut.edu.cn/sdutoj/problem.php?action=showproblem&problemid=3144
题意:
给你一个数集S的大小n,请告诉我将它划分为集合的方法总数ans是多少?
分析:
因为这题n是5000,直接算会超内存,所以需要打表。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <map>
#include <algorithm>
#define LL __int64
const int maxn = 5e3 + 10;
const int mo = 1e9 + 7;
using namespace std;
LL f[maxn][maxn];
void init()
{
LL i, j;
f[0][0] = 1;
for(i = 1; i <= maxn-1; i++)
for(j = 1; j <= i; j++)
f[i][j] = (j*f[i-1][j] + f[i-1][j-1])%mo;
}
int main()
{
LL i, j;
LL ans;
init();
FILE *fp;
fp = fopen("out.txt", "wt");
for(j = 1; j <= 5000; j++)
{
ans = 0;
for(i = 1; i <= j; i++)
ans = (ans + f[j][i])%mo;
fprintf(fp, "%I64dn", ans);
}
fclose(fp);
return 0;
}
矩阵快速幂
2015/03/07 | ACM算法学习 | bfshm | 暂无评论 | 730 views
hdu 5171 GTY’s birthday gift
题目链接
问题描述
GTY的朋友ZZF的生日要来了,GTY问他的基友送什么礼物比较好,他的一个基友说送一个可重集吧!于是GTY找到了一个可重集S,GTY能使用神犇魔法k次,每次可以向可重集中加入一个数 a+b(a,b∈S),现在GTY想最大化可重集的和,这个工作就交给你了。
注:可重集是指可以包含多个相同元素的集合
输入描述
多组数据(约3组),每组数据的第一行有两个数n,k(2≤n≤100000,1≤k≤1000000000) 表示初始元素数量和可使用的魔法数,第二行包含n个数a(1≤ai≤100000)表示初始时可重集的元素
输出描述
对于每组数据,模10000007输出可重集可能的最大和。
题意:
从一个数字集合中取出两个最大的相加得到的结果和原来的两个数再放回集合,求经过k次操作后,集合元素和的最大值
分析:
显然每次会从可重集中选择最大的两个进行操作,设这两数为a,b(a>=b),操作之后的数一定是操作后集合中最大的,下一次选取的数一定是a+b和a,这就形成了一个类似于斐波那契数列的东西,矩阵乘法快速幂求前n项和即可,转移矩阵如下
因为k很大,所以用矩阵快速幂加速递推。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e5 + 10;
const int mo = 1e7 + 7;
using namespace std;
LL a[maxn];
struct node
{
LL m[4][4];
};
node mul(node a, node b)
{
int i, j, k;
node c;
for(i = 0; i < 3; i++)
for(j = 0; j < 3; j++)
{
c.m[i][j] = 0;
for(k = 0; k < 3; k++)
c.m[i][j] += (a.m[i][k]*b.m[k][j])%mo;
}
return c;
}
node pow_n(node a, LL n)
{
node c;
memset(c.m, 0, sizeof(c.m));
for(int i = 0; i < 3; i++) c.m[i][i] = 1;
while(n)
{
if(n%2)
c = mul(c, a);
a = mul(a, a);
n /= 2;
}
return c;
}
int main()
{
LL n, k, i, ans, sum, x1, x2;
while(~scanf("%I64d%I64d", &n, &k))
{
sum = 0;
for(i = 0; i < n; i++)
{
scanf("%I64d", &a[i]);
sum += a[i];
}
sort(a, a+n);
x1 = a[n-2]; x2 = a[n-1];
node x;
memset(x.m, 0, sizeof(x.m));
x.m[0][0] = x.m[0][1] = x.m[0][2] = 1;
x.m[1][1] = x.m[1][2] = 1;
x.m[2][1] = 1;
node res = pow_n(x, k);
ans = (sum*res.m[0][0] + x2*res.m[0][1] + x1*res.m[0][2])%mo;
printf("%I64dn", ans);
}
return 0;
}博弈论(sg函数)
2015/03/05 | ACM算法学习 | bfshm | 暂无评论 | 841 views
hdu 1848 Fibonacci again and again
Problem Description
任何一个大学生对菲波那契数列(Fibonacci numbers)应该都不会陌生,它是这样定义的:
F(1)=1;
F(2)=2;
F(n)=F(n-1)+F(n-2)(n>=3);
所以,1,2,3,5,8,13……就是菲波那契数列。
在HDOJ上有不少相关的题目,比如1005 Fibonacci again就是曾经的浙江省赛题。
今天,又一个关于Fibonacci的题目出现了,它是一个小游戏,定义如下:
1、 这是一个二人游戏;
2、 一共有3堆石子,数量分别是m, n, p个;
3、 两人轮流走;
4、 每走一步可以选择任意一堆石子,然后取走f个;
5、 f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量);
6、 最先取光所有石子的人为胜者;
假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢。
Input
输入数据包含多个测试用例,每个测试用例占一行,包含3个整数m,n,p(1<=m,n,p<=1000)。
m=n=p=0则表示输入结束。
Output
如果先手的人能赢,请输出“Fibo”,否则请输出“Nacci”,每个实例的输出占一行。
Sample Input
1 1 1
1 4 1
0 0 0
Sample Output
Fibo
Nacci
分析:整理一下
必败态:当前即将操作的一方不可能必胜的状态(例如4个石子)
后继:如果当前状态为x,采取某一个合法操作后,状态变成y,那么y是x的后继(例如2个石子是5个石子的后继)
如果一个状态,所有的一个后继是必胜态,那么这个状态是必败态;
如果一个状态,存在一个后继是必败态,那么这个状态是必胜态
是不是所有博弈都可以用SG函数?
显然不是!!!!
限制条件:
二人游戏,轮流操作,不能轮空
能进行的操作是有限的
能进行的操作只取决于当前的状态,而与过去无关,与操作的人无关
游戏只会一直走向结束,不会遇到过去的状态
某一方无法操作即结束
显然麻将,斗地主都不能用这种方法。但是!无禁手的五子棋是可以的,而且的确有先手必胜的策略,但是奈何状态十分复杂,人脑难以胜任。即使是普通的电脑也无法存下所有的状态。而中国象棋,国际象棋,由于双方移动棋子与选手有关,所以也不适用
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e3 + 10;
const int mo = 1e9 + 7;
using namespace std;
int op[20], sg[maxn], tmp[20];
//sg[]数组,sg函数值, 表示有多少个的时候的(必胜或必败)状态
//op[], 操作集合, 表示每次可以取几个
//tmp[],标记集合元素,这道题最多有0-15个数在1000之内,所以0-15就行。
void init()
{
op[0] = 1; op[1] = 2;
for(int i = 2; i <= 15; i++)
op[i] = op[i-1] + op[i-2];
}
void getsg()
{
int i, j;
sg[0] = 0;
for(i = 0; i <= 1000; i++)
{
for(j = 0; j <= 15; j++)
tmp[j] = 0;
for(j = 0; j<15 && op[j]<=i; j++)
tmp[sg[i-op[j]]] = 1; //标记
for(j = 0; j <= 15; j++)
if(tmp[j] == 0) //从小到大找到最小的没出现的元素
{
sg[i] = j;
break;
}
}
}
int main()
{
int m, n, p;
init();
while(~scanf("%d%d%d", &m, &n, &p))
{
if(m==0 && n==0 && p==0)
break;
getsg();
int ans = (sg[m]^sg[n]^sg[p]);
if(ans!=0) // !=0时 先手获胜
cout<<"Fibo"<<endl;
else
cout<<"Nacci"<<endl;
}
return 0;
}
hdu 3032 Nim or not Nim?
Input
Input contains multiple test cases. The first line is an integer 1 ≤ T ≤ 100, the number of test cases. Each case begins with an integer N, indicating the number of the heaps, the next line contains N integers s[0], s[1], …., s[N-1], representing heaps with s[0], s[1], …, s[N-1] objects respectively.(1 ≤ N ≤ 10^6, 1 ≤ S[i] ≤ 2^31 – 1)
题意:
n堆石子,游戏双方每次可以取某一堆的任意个,不可以不取,还可以把一堆分成两堆,最后没办法操作的一方为输家。
(1)从一堆石子中取走任意多个(2)将一堆数量不少于2的石子分成都不为空的两堆。
分析:
因为数据比较大,所以一定是找规律的,先用sg函数打表就会发现,当n%4==3时,sg[n] = n+1; n%4==0时,sg[n] = n-1;
其他时候sg[n] = n;
打表代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e6 + 10;
const int mo = 1e9 + 7;
using namespace std;
int sg[1000 + 10];
int getsg(int x)
{
int i, tmp[1000 + 10];
memset(tmp, 0, sizeof(tmp));
if(sg[x] != -1)
return sg[x];
for(i = x-1; i >= 0; i--) //任意取
tmp[getsg(i)] = 1;
for(i = 1; i <= x/2; i++) //划分为两堆
{
int ans = 0;
ans ^= getsg(i);
ans ^= getsg(x-i);
tmp[ans] = 1;
}
for(i = 0; ; i++)
if(tmp[i]==0)
{
sg[x] = i;
return i;
}
}
int main()
{
memset(sg, -1, sizeof(sg));
for(int i = 0; i <= 100; i++)
{
int ans = getsg(i);
printf("%d %dn", i, ans);
}
return 0;
}
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e6 + 10;
const int mo = 1e9 + 7;
using namespace std;
int main()
{
int t, n, i, a, ans;
scanf("%d", &t);
while(t--)
{
ans = 0;
scanf("%d", &n);
for(i = 0; i < n; i++)
{
scanf("%d", &a);
if(a%4==3)
ans ^= (a+1);
else if(a%4==0)
ans ^= (a-1);
else
ans ^= a;
}
if(ans!=0)
cout<<"Alice"<<endl;
else
cout<<"Bob"<<endl;
}
return 0;
}
Codeforces Round #290 (Div. 2) D. Fox And Jumping(思维题 滚动更新值)
2015/02/07 | ACM算法学习 | bfshm | 暂无评论 | 878 views
题目链接
D. Fox And Jumping
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output
Fox Ciel is playing a game. In this game there is an infinite long tape with cells indexed by integers (positive, negative and zero). At the beginning she is standing at the cell 0.
There are also n cards, each card has 2 attributes: length li and cost ci. If she pays ci dollars then she can apply i-th card. After applying i-th card she becomes able to make jumps of length li, i. e. from cell x to cell (x - li) or cell (x + li).
She wants to be able to jump to any cell on the tape (possibly, visiting some intermediate cells). For achieving this goal, she wants to buy some cards, paying as little money as possible.
If this is possible, calculate the minimal cost.
Input
The first line contains an integer n (1 ≤ n ≤ 300), number of cards.
The second line contains n numbers li (1 ≤ li ≤ 109), the jump lengths of cards.
The third line contains n numbers ci (1 ≤ ci ≤ 105), the costs of cards.
Output
If it is impossible to buy some cards and become able to jump to any cell, output -1. Otherwise output the minimal cost of buying such set of cards.
Sample test(s)
Note
题意:有N个数字,下面分别代表这N个数字的值 和 购买N个数字的费用,要使数字的组合(+x 或者 -x)后,能够组成任意数字,
求最小的花费。
分析:
其实就是组合成 1 就可以了。
而获得1的话 只需要我们选的数的gcd = 1即可。
设 有整数x,y,要使得x y能构造任意一个整数,充要条件就是gcd(x, y)=1
不断的求与 之前的值的gcd(),滚动更新最小费
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <map>
#include <algorithm>
#define LL __int64
const int maxn = 3e2 + 10;
using namespace std;
int l[maxn], c[maxn], pre, cur;
map<int, int>mp[2];
map<int, int>::iterator it;
int gcd(int a, int b)
{
return b==0?a:gcd(b, a%b);
}
void update(int x, int y)
{
if(mp[cur].count(x))
{
if(y < mp[cur][x]) //gcd()相同的情况下,找一个最小的费用
mp[cur][x] = y;
}
else mp[cur][x] = y;
}
int main()
{
int i, n;
while(~scanf("%d", &n))
{
for(i = 1; i <= n; i++)
scanf("%d", &l[i]);
for(i = 1; i <= n; i++)
scanf("%d", &c[i]);
pre = 0; cur = 1;
mp[0][l[1]] = c[1]; //分别代表数组下标,最大公约数,费用。
for(i = 2; i <= n; i++)
{
mp[cur].clear();
mp[cur][l[i]] = c[i];
for(it = mp[pre].begin(); it != mp[pre].end(); it++)
{
int x = it->first;
int y = it->second;
update(x, y);
update(gcd(x, l[i]), y+c[i]);
}
swap(pre, cur); //相当于滚动数组
}
if(!mp[pre].count(1)) //如果gcd()==1的个数为0,则没有
cout<<-1<<endl;
else
cout<<mp[pre][1]<<endl;
}
return 0;
}
poj 3301 Texas Trip(三分 旋转点)
2015/02/04 | ACM算法学习 | bfshm | 暂无评论 | 1232 views
题目链接
Texas Trip
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 4133 Accepted: 1262
Description
After a day trip with his friend Dick, Harry noticed a strange pattern of tiny holes in the door of his SUV. The local American Tire store sells fiberglass patching material only in square sheets. What is the smallest patch that Harry needs to fix his door?
Assume that the holes are points on the integer lattice in the plane. Your job is to find the area of the smallest square that will cover all the holes.
Input
The first line of input contains a single integer T expressed in decimal with no leading zeroes, denoting the number of test cases to follow. The subsequent lines of input describe the test cases.
Each test case begins with a single line, containing a single integer n expressed in decimal with no leading zeroes, the number of points to follow; each of the following n lines contains two integers x and y, both expressed in decimal with no leading zeroes, giving the coordinates of one of your points.
You are guaranteed that T ≤ 30 and that no data set contains more than 30 points. All points in each data set will be no more than 500 units away from (0,0).
Output
Print, on a single line with two decimal places of precision, the area of the smallest square containing all of your points.
Sample Input
2
4
-1 -1
1 -1
1 1
-1 1
4
10 1
10 -1
-10 1
-10 -1
Sample Output
4.00
242.00
Source
题意:
给定二维平面的n个点,要求一个面积最小的正方形,使其能覆盖所有的点。
分析:
标准正放正方形的比较好做只要找出 minx,miny,maxx,maxy即可
但是最小正方形不一定是标准正放,
由于题目中的点只有30个
因此有两种穷举的思路(不是精确解)
一:穷举每个小角度变化值的正方形的四条边,看是否所有点在四条边内部,本方法要求计算几何水平很高
二:穷举每个点对角度坐标表换的值,然后求标准正放的最小正方形即可,相对第一种方法此方法简单明了,且编码容易
角度A1转换弧度A2: A2=A1*PI/180
弧度A2转换角度A1: A1=A2*180/PI
这个题我用的是旋转所有点。
让正方形不要动,所有点进行旋转变换,这样结果是不会变形的.
旋转公式: x1 = x[i]*cos(a) – y[i]*sin(a); y1 = x[i]*sin(a) + y[i]*cos(a);
可以证明它们都是凸性函数,故他们差的最大值也是凸性函数,故可以用三分法,对旋转角度进行三分,求出最小的满足要求的正方形。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <algorithm>
#define LL __int64
const int maxn = 1e2 + 10;
const int INF = 1<<28;
const double pi = acos(-1.0);
const double eps = 1e-12;
using namespace std;
double x[maxn], y[maxn];
int n;
double cal(double a)
{
double min_x, min_y, max_x, max_y;
double x1, y1;
min_x = (double)INF; min_y = (double)INF;
max_x = (double)-INF; max_y = (double)-INF;
for(int i = 0; i < n; i++)
{
x1 = x[i]*cos(a) - y[i]*sin(a);
y1 = x[i]*sin(a) + y[i]*cos(a);
if(x1-max_x > 0) max_x = x1;
if(x1-min_x < 0) min_x = x1;
if(y1-max_y > 0) max_y = y1;
if(y1-min_y < 0) min_y = y1;
}
double ret = max(max_x - min_x, max_y - min_y);
return ret*ret;
}
int main()
{
int t, i;
double le, ri, mid, midmid, ans;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for(i = 0; i < n; i++)
scanf("%lf%lf", &x[i], &y[i]);
le = 0; ri = pi;
do
{
mid = (le + ri)/2.0;
midmid = (mid + ri)/2.0;
double ret1 = cal(mid);
double ret2 = cal(midmid);
if(ret1 < ret2)
{
ri = midmid;
ans = ret1;
}
else
{
le = mid;
ans = ret2;
}
} while(fabs(ri - le)>eps);
printf("%.2fn", ans);
}
return 0;
}
UVA 10003 Cutting Sticks(区间dp)
2015/02/02 | ACM算法学习 | bfshm | 暂无评论 | 1614 views
题目链接 就不贴了,打不开
Cutting Sticks
Cutting Sticks
You have to cut a wood stick into pieces. The most affordable company, The Analog Cutting Machinery, Inc. (ACM), charges money according to the length of the stick being cut. Their procedure of work requires that they only make one cut at a time.
It is easy to notice that different selections in the order of cutting can led to different prices. For example, consider a stick of length 10 meters that has to be cut at 2, 4 and 7 meters from one end. There are several choices. One can be cutting first at 2, then at 4, then at 7. This leads to a price of 10 + 8 + 6 = 24 because the first stick was of 10 meters, the resulting of 8 and the last one of 6. Another choice could be cutting at 4, then at 2, then at 7. This would lead to a price of 10 + 4 + 6 = 20, which is a better price.
Your boss trusts your computer abilities to find out the minimum cost for cutting a given stick.
Input
The input will consist of several input cases. The first line of each test case will contain a positive number l that represents the length of the stick to be cut. You can assume l < 1000. The next line will contain the number n ( n < 50) of cuts to be made.
The next line consists of n positive numbers ci ( 0 < ci < l) representing the places where the cuts have to be done, given in strictly increasing order.
An input case with l = 0 will represent the end of the input.
Output
You have to print the cost of the optimal solution of the cutting problem, that is the minimum cost of cutting the given stick. Format the output as shown below.
Sample Input
100
3
25 50 75
10
4
4 5 7 8
0
Sample Output
The minimum cutting is 200.
The minimum cutting is 22.
题意:
有一根长度为l的木棍,木棍上面有m个切割点,每一次切割都要付出当前木棍长度的代价,问怎样切割有最小代价。
你的任务是替一家叫Analog Cutting Machinery (ACM)的公司切割木棍。 切割木棍的成本是根据木棍的长度而定。 而且切割木棍的时候每次只切一段。
很显然的,不同切割的顺序会有不同的成本。 例如:有一根长10公尺的木棍必须在第2、4、7公尺的地方切割。 这个时候就有几种选择了。你可以选择先切2公尺的地方,然后切4公尺的地方,最后切7公尺的地方。这样的选择其成本为:10+8+6=24。 因为第一次切时木棍长10公尺,第二次切时木棍长8公尺,第三次切时木棍长6公尺。 但是如果你选择先切4公尺的地方,然后切2公尺的地方,最后切7公尺的地方,其成本为:10+4+6=20,这成本就是一个较好的选择。
贴一个题解链接:http://blog.csdn.net/zhongshijunacm/article/details/38977537
区间DP的定义:
区间动态规划问题一般都是考虑,对于每段区间,他们的最优值都是由几段更小区间的最优值得到,是分治思想的一种应用,将一个区间问题不断划分为更小的区间直至一个元素组成的区间,枚举他们的组合,求合并后的最优值。
解法:
设F[i,j](1<=i<=j<=n)表示区间[i,j]内的数字相加的最小代价 , 最小区间F[i,i]=0(一个数字无法合并,∴代价为0)每次用变量k(i<=k<=j-1)将区间分为[i,k]和[k+1,j]两段
区间DP模板,代码:
for(intp = 1 ; p <= n ; p++){ //p是区间的长度,作为阶段
for(int i = 1 ; i <= n ; i++){ //i是穷举区间的起点
int j = i+p-1; //j为区间的终点
for(int k = i ; k < j ; k++)//状态转移
dp[i][j] = min{dp[i][k]+dp[k+1][j]+w[i][j]};//这个是看题目意思,有的是要从k开始不是k+1
dp[i][j]= max{dp[i][k]+dp[k+1][j]+w[i][j]};
}
}
分析:
这个题其实就是把一整个区间拆分,上面的链接里讲了 这个题 还有 一个huffman树的一个题,huffman树是
把区间合并成一个区间。
这个题的做法首先加两个点0 和 N,d[i][j]代表从切割点i 到 切割点j 的最小的费用,然后从小区间 到 大区间dp。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e2 + 10;
const int INF = 1<<28;
using namespace std;
int c[maxn], d[maxn][maxn];
int main()
{
int l, n, i, j, k;
int s, e;
while(~scanf("%d", &l))
{
if(l == 0) break;
cin>>n;
for(i = 1; i <= n; i++)
cin>>c[i];
n ++;
c[0] = 0; c[n] = l;
for(i = 0; i <= n; i++)
for(j = i+1; j <= n; j++)
if(j == i+1) d[i][j] = 0; //相邻的区间初始为0,下面可能会用到
else d[i][j] = INF; //初始化
for(i = 1; i <= n; i++) //枚举区间长度
for(s = 0; s <= n; s++) //区间起点
{
e = s + i; //区间终点。
if(e > n) break;
for(k = s+1; k < e; k++)
d[s][e] = min(d[s][e], d[s][k] + d[k][e] + c[e] - c[s]);
}
printf("The minimum cutting is %d.n", d[0][n]);
}
return 0;
}
poj 2002 Squares (哈希表)
2015/01/29 | ACM算法学习 | bfshm | 暂无评论 | 817 views
题目链接
Squares
Time Limit: 3500MS Memory Limit: 65536K
Total Submissions: 16821 Accepted: 6400
Description
A square is a 4-sided polygon whose sides have equal length and adjacent sides form 90-degree angles. It is also a polygon such that rotating about its centre by 90 degrees gives the same polygon. It is not the only polygon with the latter property, however, as a regular octagon also has this property.
So we all know what a square looks like, but can we find all possible squares that can be formed from a set of stars in a night sky? To make the problem easier, we will assume that the night sky is a 2-dimensional plane, and each star is specified by its x and y coordinates.
Input
The input consists of a number of test cases. Each test case starts with the integer n (1 <= n <= 1000) indicating the number of points to follow. Each of the next n lines specify the x and y coordinates (two integers) of each point. You may assume that the points are distinct and the magnitudes of the coordinates are less than 20000. The input is terminated when n = 0.
Output
For each test case, print on a line the number of squares one can form from the given stars.
Sample Input
4
1 0
0 1
1 1
0 0
9
0 0
1 0
2 0
0 2
1 2
2 2
0 1
1 1
2 1
4
-2 5
3 7
0 0
5 2
0
Source
题意:
给你1000个点,让你在这些点中,找到4个点,使其成为一个正方形的顶点。问有多少对这种点。
分析:
选两个点,看剩下的两个点是否是给出来的。
map写法:对x, y的处理是通过 对x乘30000(因为题目最大坐标是20000)加y。通过对这个结果map[],
枚举两个点后,通过计算正方形的另外两个点 是否给出,来判断有多少正方形。重复计算了,所以最后结果除2.
注意map的时候查找用mp.find(tmp) == mp.end()。
如果用mp[tmp] == 0,这种写法会超时,可能是因为这样对map[]里又插入了新的数。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <map>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e3 + 10;
using namespace std;
struct node
{
int x, y;
}p[maxn];
bool cmp(node a, node b)
{
if(a.x == b.x)
return a.y < b.y;
return a.x < b.x;
}
int main()
{
int n, i, j, ans;
int tmp;
while(~scanf("%d", &n) && n)
{
ans = 0;
map<int, int>mp;
for(i = 0; i < n; i++)
{
scanf("%d%d", &p[i].x, &p[i].y);
tmp = p[i].x * 30000 + p[i].y;
mp[tmp] = 1;
}
sort(p, p+n, cmp);
for(i = 0; i < n; i++)
for(j = i+1; j < n; j++)
{
int x1, y1;
x1 = p[i].x + p[j].y - p[i].y;
y1 = p[i].y + p[i].x - p[j].x;
tmp = x1 * 30000 + y1;
if(mp.find(tmp) == mp.end()) continue;
x1 = p[j].x + p[j].y - p[i].y;
y1 = p[j].y + p[i].x - p[j].x;
tmp = x1 * 30000 + y1;
if(mp.find(tmp) == mp.end()) continue;
ans ++;
}
printf("%dn", ans/2);
}
return 0;
}
POJ 1521 Entropy(huffman树)
2015/01/27 | ACM算法学习 | bfshm | 暂无评论 | 982 views
题目链接
题意:
题真的是太长了。
给你一个字符串,每一个字符在计算机中用8位编码表示,所以给你字符串AAAAABCD,
它在计算机中要占用64位,可是为了节省内存,希望你能设计出一种编码,使得内存的占用尽可能的少,
即字符串的位数表示最少,输出 原本占几位,编码后占几位,还要输出压缩比。
即让用哈夫曼编码求给定字符串的 编码总位数,编码就是0 1 0 1 这种。
分析:
求哈夫曼树可以用优先队列这种偷懒一点的方法,
下面的代码是 建一个小顶堆,用队列保存每个字符出现的次数,每次把最小的两个放到树的最下面,
并令其出队列,然后把这两个的和放进队列。把每次最小的两个加加到总和里,最后总和就是要求的最小
编码总位数,如果画一下树的话会很清楚,看到树最下面的节点 都要 加 他们的深度 次和。
不过这种用优先队列的方法,如果要求 编码和译码的话,就不是很方便了,就要建树了。
可以参照一下这两篇博客:
http://www.cnblogs.com/newpanderking/archive/2012/11/14/2769367.html
http://blog.csdn.net/u010720564/article/details/9004134
注意字符只有一种的情况特判一下。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <vector>
#include <queue>
#include <algorithm>
#define LL __int64
const int maxn = 2e5 + 10;
const int INF = 1<<28;
using namespace std;
int f[maxn];
char s[maxn];
int main()
{
int i, len;
int a, b, ans;
while(~scanf("%s", &s))
{
priority_queue<int, vector<int>, greater<int> >q;
memset(f, 0, sizeof(f));
ans = 0;
if(strcmp(s, "END")==0) break;
len = strlen(s);
for(i = 0; i < len; i++)
f[s[i]] ++;
for(i = 0; i < 280; i++)
if(f[i]>0)
q.push(f[i]);
if(q.size()==1)
{
ans = q.top();
}
else
while(!q.empty())
{
a = q.top();
q.pop();
if(q.empty())
{
break;
}
b = q.top();
q.pop();
ans += a+b;
q.push(a+b);
}
printf("%d %d %.1fn", len*8, ans, len*8.0/ans);
}
return 0;
}下面贴一下建树的 解这道题的方法:
#include <iostream>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef struct Huffman_trie
{
int deep;//深度
int freq;//频度(即哈夫曼树中的权重)
Huffman_trie *left,*right;
//优先权队列中用于排序比较的方法,不懂的建议先学一学优先权队列
friend bool operator<(Huffman_trie a,Huffman_trie b)
return a.freq>b.freq;
}Huffman_trie;
Huffman_trie trie[300];//哈夫曼树节点
Huffman_trie *root;
int len,count_num,index,sum;
priority_queue<Huffman_trie> pq;
void huffman()
{
sum=0;
root = (Huffman_trie*)malloc(sizeof(Huffman_trie));
for(int i=0;i<index;i++)
pq.push(trie[i]);
while(pq.size()>1) //建树
{
Huffman_trie *h1 = (Huffman_trie*)malloc(sizeof(Huffman_trie));
*h1 = pq.top();
pq.pop();
Huffman_trie *h2 = (Huffman_trie*)malloc(sizeof(Huffman_trie));
*h2 = pq.top();
pq.pop();
Huffman_trie h3;
h3.left=h1;
h3.right=h2;
h3.freq=h1->freq+h2->freq;
pq.push(h3);
}
*root = pq.top();
pq.pop();
root->deep=0;
queue<Huffman_trie>q;
q.push(*root);
while(!q.empty())
{
Huffman_trie ht=q.front();
q.pop();
if(ht.left!=NULL)
{
ht.left->deep=ht.deep+1;
q.push(*ht.left);
}
if(ht.right!=NULL)
{
ht.right->deep=ht.deep+1;
q.push(*ht.right);
}
if(!ht.left&&!ht.right)
sum+=ht.deep*ht.freq;
}
}
int main()
{
char str[1000];
while(scanf("%s",str)!=EOF&&strcmp(str,"END")!=0)
{
len = strlen(str);
str[len]='!';
sort(str,str+len); //对字母排序
count_num=1;
index=0;
for(int i=1;i<=len;i++)
{
if(str[i]!=str[i-1])
{
trie[index++].freq=count_num;
count_num=1;
}else count_num++; //统计个数
}
if(index==1)
printf("%d %d 8.0n",len*8,len);
else
{
huffman();
printf("%d %d %.1lfn",len*8,sum,len*8*1.0/sum);
}
}
return 0;
}
Codeforces Round#287(div2) C. Guess Your Way Out! (思维 dfs)
2015/01/26 | ACM算法学习 | bfshm | 暂无评论 | 818 views
题目链接
Amr bought a new video game “Guess Your Way Out!”. The goal of the game is to find an exit from the maze that looks like a perfect binary tree of height h. The player is initially standing at the root of the tree and the exit from the tree is located at some leaf node.
Let’s index all the leaf nodes from the left to the right from 1 to 2h. The exit is located at some node n where 1 ≤ n ≤ 2h, the player doesn’t know where the exit is so he has to guess his way out!
Amr follows simple algorithm to choose the path. Let’s consider infinite command string “LRLRLRLRL…” (consisting of alternating characters ‘L‘ and ‘R‘). Amr sequentially executes the characters of the string using following rules:
Character ‘L‘ means “go to the left child of the current node”;
Character ‘R‘ means “go to the right child of the current node”;
If the destination node is already visited, Amr skips current command, otherwise he moves to the destination node;
If Amr skipped two consecutive commands, he goes back to the parent of the current node before executing next command;
If he reached a leaf node that is not the exit, he returns to the parent of the current node;
If he reaches an exit, the game is finished.
Now Amr wonders, if he follows this algorithm, how many nodes he is going to visit before reaching the exit?
Input
Input consists of two integers h, n (1 ≤ h ≤ 50, 1 ≤ n ≤ 2h).
Output
Output a single integer representing the number of nodes (excluding the exit node) Amr is going to visit before reaching the exit by following this algorithm.
Hint
A perfect binary tree of height h is a binary tree consisting of h + 1 levels. Level 0 consists of a single node called root, level h consists of 2h nodes called leaves. Each node that is not a leaf has exactly two children, left and right one.
Following picture illustrates the sample test number 3. Nodes are labeled according to the order of visit.
题意:
1、L走到节点的左子树。
2、R走到节点的右子树。
3、如果要到节点已经被访问过,跳过该步。
4、如果连续跳过两步,返回该节点的父节点。
5、如果到了非出口的叶子节点,返回该节点的父节点。
计算出从根到那个节点的路径。(LR的序列)
一个深度为h的满二叉树,假设叶子编号为1–2^(h-1),给出深度和叶子编号,按照LRLRLRLR……的方式
遍历二叉树,问到达给定 的叶子编号之前访问了多少节点。
层次从0开始,即0–h层。
分析:
根据指令递归判点在左右子树还是右子树的情况。
做法就是分成4种情况
出口在左子树,指令是L
出口在左子树,指令是R
出口在右子树,指令是L
出口在右子树,指令是R
根据叶子n的相对位置,二分左右树。
按照上面的四种情况递归。
比赛的时候想太多了,光想着如何解决从左子树 或者 右子树 出来之后的 到左或者右,
其实不用考虑这个,只是需要考虑根据相对位置看 能否 走完这一整个左 或者 右子树,
比如现在进入右子树,而且要求的在右子树的话,下一次一定是 往左走。
注意1LL<<() 的#include <iostream>
#include <cstdio>
#include <cmath>
#include <map>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 2e5 + 10;
const int INF = 1<<28;
using namespace std;
LL dfs(LL h, LL n, LL f) //h为当前层,n为叶子的编号,是相对位置,f为当前层应该向哪走,0表示左1右
{
if(h == 0) return 0;
if(f==0)
{
if(n > (1LL<<(h-1)))
return 1 + ((1LL<<h)-1) + dfs(h-1, n-(1LL<<(h-1)), 0);
else
return 1 + dfs(h-1, n, 1);
}
else
{
if(n > (1LL<<(h-1)))
return 1 + dfs(h-1, n-(1LL<<(h-1)), 0);
else
return 1 + ((1LL<<h)-1) + dfs(h-1, n, 1);
}
}
int main()
{
LL h, n;
while(~scanf("%I64d%I64d", &h, &n))
{
printf("%I64dn", dfs(h, n, 0));
}
return 0;
}时候,一定要注意LL的类型转换,不然会错,因为1默认为int。
poj 1113 Wall(凸包)
2015/01/23 | ACM算法学习 | bfshm | 暂无评论 | 933 views
题目链接
Wall
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 30626 Accepted: 10320
Description
Once upon a time there was a greedy King who ordered his chief Architect to build a wall around the King’s castle. The King was so greedy, that he would not listen to his Architect’s proposals to build a beautiful brick wall with a perfect shape and nice tall towers. Instead, he ordered to build the wall around the whole castle using the least amount of stone and labor, but demanded that the wall should not come closer to the castle than a certain distance. If the King finds that the Architect has used more resources to build the wall than it was absolutely necessary to satisfy those requirements, then the Architect will loose his head. Moreover, he demanded Architect to introduce at once a plan of the wall listing the exact amount of resources that are needed to build the wall.Your task is to help poor Architect to save his head, by writing a program that will find the minimum possible length of the wall that he could build around the castle to satisfy King’s requirements.
The task is somewhat simplified by the fact, that the King’s castle has a polygonal shape and is situated on a flat ground. The Architect has already established a Cartesian coordinate system and has precisely measured the coordinates of all castle’s vertices in feet.
Input
The first line of the input file contains two integer numbers N and L separated by a space. N (3 <= N <= 1000) is the number of vertices in the King’s castle, and L (1 <= L <= 1000) is the minimal number of feet that King allows for the wall to come close to the castle.Next N lines describe coordinates of castle’s vertices in a clockwise order. Each line contains two integer numbers Xi and Yi separated by a space (-10000 <= Xi, Yi <= 10000) that represent the coordinates of ith vertex. All vertices are different and the sides of the castle do not intersect anywhere except for vertices.
Output
Write to the output file the single number that represents the minimal possible length of the wall in feet that could be built around the castle to satisfy King’s requirements. You must present the integer number of feet to the King, because the floating numbers are not invented yet. However, you must round the result in such a way, that it is accurate to 8 inches (1 foot is equal to 12 inches), since the King will not tolerate larger error in the estimates.
Hint
结果四舍五入就可以了
题意:
平面上有一些点,求包围这些点且距离最外层的点的距离为L的图形的周长,所谓最外层即这些点的凸包。
分析:
所有的拐点处包围图形的总长度正好为一个半径为L的圆形的周长,所以所求答案为凸包长度+2*Pi*L
#include <iostream>
#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e5 + 10;
const double pi = acos(-1);
using namespace std;
int top, s[maxn], n;
struct point
{
int x, y;
}p[maxn], tt;
int cross(point a, point b, point c)
{
return ((b.x-a.x) * (c.y-a.y) - (c.x-a.x) * (b.y-a.y));
}
int dis(point a, point b)
{
return ((a.x-b.x) * (a.x-b.x) + (a.y-b.y) * (a.y-b.y));
}
bool cmp(point a, point b)
{
int ans = cross(tt, a, b);
if( ans > 0 || (ans == 0 && dis(tt, a) < dis(tt, b)) ) //一般都要写成距离小的靠前,不过这个题一样
return true;
return false;
}
void graham()
{
int i;
s[0] = 0; s[1] = 1; top = 1;
for(i = 2; i < n; i++)
{
while(top && cross(p[s[top-1]], p[s[top]], p[i]) < 0) top --;
s[++top] = i;
}
top ++;
}
int main()
{
int i, l, u;
double ans;
while(~scanf("%d%d", &n, &l))
{
ans = 2*pi*l;
u = 0;
for(i = 0; i < n; i++)
{
scanf("%d%d", &p[i].x, &p[i].y);
if(p[i].y < p[u].y || (p[i].y==p[u].y && p[i].x < p[u].x))
u = i;
}
swap(p[0].x, p[u].x);
swap(p[0].y, p[u].y);
tt = p[0];
sort(p+1, p+n, cmp);
graham();
for(i = 0; i < top-1; i++)
ans += (double)sqrt(dis(p[s[i]], p[s[i+1]]));
ans += (double)sqrt(dis(p[s[0]], p[s[top-1]]));
printf("%.0fn", ans);
}
return 0;
}
poj 2187 Beauty Contest(凸包 + 旋转卡壳)
2015/01/22 | ACM算法学习 | bfshm | 暂无评论 | 776 views
题目链接
Beauty Contest
Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 29054 Accepted: 9020
Description
Bessie, Farmer John’s prize cow, has just won first place in a bovine beauty contest, earning the title ‘Miss Cow World’. As a result, Bessie will make a tour of N (2 <= N <= 50,000) farms around the world in order to spread goodwill between farmers and their cows. For simplicity, the world will be represented as a two-dimensional plane, where each farm is located at a pair of integer coordinates (x,y), each having a value in the range -10,000 … 10,000. No two farms share the same pair of coordinates.Even though Bessie travels directly in a straight line between pairs of farms, the distance between some farms can be quite large, so she wants to bring a suitcase full of hay with her so she has enough food to eat on each leg of her journey. Since Bessie refills her suitcase at every farm she visits, she wants to determine the maximum possible distance she might need to travel so she knows the size of suitcase she must bring.Help Bessie by computing the maximum distance among all pairs of farms.
Input
* Line 1: A single integer, N* Lines 2..N+1: Two space-separated integers x and y specifying coordinate of each farm
Output
* Line 1: A single integer that is the squared distance between the pair of farms that are farthest apart from each other.
题意:
给你 N 个点, 求所有点中最远两点距离。
分析:
点有50000个,直接暴力枚举的话,会超时。
最远距离两个点一定在凸包上,建立好背包后,直接套用旋转卡壳找直径。
稍加分析 我们可以发现 凸包上的点依次与对应边产生的距离成单峰函数。
这样我们再找到一个点 使下一个点的距离小于当前的点时就可以停止了
而且随着对应边的旋转 最远点也只会顺着这个方向旋转 我们可以从上一次的对踵点开始继续寻找这一次的
由于内层while循环的执行次数取决于j增加次数 j最多增加O(N)次
所以求出所有对踵点的时间复杂度为O(N)。
推荐几个博客:
http://www.xuebuyuan.com/552925.html
http://www.cnblogs.com/xdruid/archive/2012/07/01/2572303.html
http://www.cnblogs.com/Booble/archive/2011/04/03/2004865.html
在所有点中选取y坐标最小的一点H,当作基点。如果存在多个点的y坐标都为最小值,
则选取x坐标最小的一点。坐标相同的点应排除。
下面代码里的排序方式为以 基点 开始 逆时针 排列点。相同斜率的 距离大的靠前,
因为这个要求最大直径,最大直径一定在一条线段的两端,距离大的靠前,可以把距离小的排除凸包去。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e5 + 10;
using namespace std;
//s为放凸包的栈
int top, s[maxn], n;
struct point
{
int x, y;
}p[maxn], tt; //tt为极角排序的参照点。
int cross(point a, point b, point c)
{
return ((b.x-a.x) * (c.y-a.y) - (c.x-a.x) * (b.y-a.y));
}
int dis(point a, point b)
{
return ((a.x-b.x) * (a.x-b.x) + (a.y-b.y) * (a.y-b.y));
}
//注意这里的距离取的是大的靠前
bool cmp(point a, point b)
{
int ans = cross(tt, a, b);
if( ans > 0 || (ans == 0 && dis(tt, a) < dis(tt, b)) ) return true;
return false;
}
//凸包
void graham()
{
int i;
s[0] = 0; s[1] = 1; top = 1;
for(i = 2; i < n; i++)
{
while(top && cross(p[s[top-1]], p[s[top]], p[i]) < 0) top --;
s[++top] = i;
}
top ++;
}
//旋转卡壳
int rc()
{
int q, ans = 0;
q = 1;
ans = dis(p[s[0]], p[s[1]]);
for(int i = 0; i != top; i ++)
{
while(abs(cross(p[s[(i+1)%top]], p[s[i%top]],p[s[(q+1)%top]])) > abs(cross(p[s[(i+1)%top]], p[s[i%top]],p[s[q%top]])))
q = (q + 1)%top;
ans = max(ans , max(dis(p[s[(i+1)%top]],p[s[q]]), dis(p[s[i%top]],p[s[q]])));
}
return ans;
}
int main()
{
int i;
while(~scanf("%d", &n))
{
for(i = 0; i < n; i++)
scanf("%d%d", &p[i].x, &p[i].y);
int u = 0;
for(i = 1; i < n; i++) //找一个最左上点为参照点
{
if(p[u].y > p[i].y || (p[u].y==p[i].y && p[u].x > p[i].x))
u = i;
}
swap(p[0].x, p[u].x);
swap(p[0].y, p[u].y);
tt = p[0];
sort(p+1, p+n, cmp);
graham();
printf("%dn", rc());
}
return 0;
}
poj 3680 Intervals(最小费用最大流+离散化)
2015/01/21 | ACM算法学习 | bfshm | 暂无评论 | 870 views
题目链接
Intervals
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 6658 Accepted: 2764
Description
You are given N weighted open intervals. The ith interval covers (ai, bi) and weighs wi. Your task is to pick some of the intervals to maximize the total weights under the limit that no point in the real axis is covered more than k times.
Input
The first line of input is the number of test case.
The first line of each test case contains two integers, N and K (1 ≤ K ≤ N ≤ 200).
The next N line each contain three integers ai, bi, wi(1 ≤ ai < bi ≤ 100,000, 1 ≤ wi ≤ 100,000) describing the intervals.
There is a blank line before each test case.
Output
For each test case output the maximum total weights in a separate line.
Sample Input
题意:
有多个区间,每个区间都是开区间,每一个区间都有一个权值,现在要保证每一个点不被覆盖k次以上,并且要是所有区间权值和最大。给n个有权(权<10w)开区间(n<200),(区间最多数到10w)保证数轴上所有数最多被覆盖k次的情况下要求总权最大,输出最大权。
分析:
将端点离散化,添加源汇
汇点与数轴上最后一个点连 容量为k,费用0
源点与数轴上第一个点连,容量为k,费用为0
这两条弧用来保证一个点不被重复覆盖k次 ( 其实我们每一次增广网络的时候相当于从起点到终点画一条线,每次选中尽量多的不重叠的区间,这样最多重复画k次那么就一定不会有点被覆盖k次以上)
然后相邻的点 i 和 i+1 连一条边 容量 INF , 费用0
然后区间(a,b)
相应点连边,容量为 1 费用为 -w。
限制的处理:s–>开始流量为k,要求总权最大,即费用最大,所以费用取负,最小费用最大流即可。对于输入区间[a,b]:w,添加边:a–>b,流量为1,费用为-w。
对于点i,i+1,添加边,费用为0,流量无穷。显然这种处理,限制了区间最多取k次,(流量控制),跑最大流能走添加的边尽量走,且越大越好(负数刚刚是最小费用)。
这个题其实是求最大费用,处理的时候只需要 把给的权值w取负,最后结果取负就行了。
最重要的是这个题的建图很巧妙,注意建图。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <map>
#include <queue>
#include <algorithm>
#define LL __int64
const int maxn = 1e3 + 10;
const int maxm = 1e5 + 10;
const int INF = 1<<28;
using namespace std;
int head[maxn], pre[maxn], dis[maxn], tol, maxflow, mincost;
bool in[maxn]; //in记录是否在队列中
int N, start, end; //N点的总数(包括源点和汇点)点从0到n-1,start源点,end汇点。
struct edge
{
int to, next, cap, cost;
}e[maxm];
void add(int u, int v, int cap, int cost)
{
e[tol].to = v;
e[tol].cap = cap;
e[tol].cost = cost;
e[tol].next = head[u];
head[u] = tol++;
e[tol].to = u;
e[tol].cap = 0;
e[tol].cost = -cost;
e[tol].next = head[v];
head[v] = tol++;
}
bool spfa(int s)
{
int i;
queue<int>q;
for(i = 0; i <= N; i++)
{
dis[i] = INF;
in[i] = false;
pre[i] = -1;
}
dis[s] = 0;
in[s] = true;
q.push(s);
while(!q.empty())
{
int tag = q.front();
in[tag] = false;
q.pop();
for(i = head[tag]; i != -1; i = e[i].next)
{
int j = e[i].to;
if(e[i].cost+dis[tag]<dis[j] && e[i].cap)
{
dis[j] = e[i].cost+dis[tag];
pre[j] = i;
if(!in[j])
{
q.push(j);
in[j] = true;
}
}
}
}
if(dis[end] == INF)
return false;
return true;
}
//计算最大流和最小费用,分别存储在maxflow和mincost中。
void mincostmaxflow()
{
int flow, i;
maxflow = 0, mincost = 0;
while(spfa(start))
{
flow = INF;
for (i = pre[end]; i != -1; i = pre[e[i^1].to])
if(e[i].cap < flow)
flow = e[i].cap;
for (i = pre[end]; i != -1; i = pre[e[i^1].to])
{
e[i].cap -= flow;
e[i^1].cap += flow;
}
maxflow += flow;
mincost += flow*dis[end];
}
}
int main()
{
int t, i, n, k;
int cnt, h[maxn], l[maxn], r[maxn], w[maxn];
scanf("%d", &t);
while(t--)
{
map<int, int>mp;
scanf("%d%d", &n, &k);
cnt = 1;
for(i = 0;i < n;i++)
{
scanf("%d%d%d", &l[i], &r[i], &w[i]);
h[cnt++] = l[i];
h[cnt++] = r[i];
}
sort(h+1, h+cnt);
cnt = unique(h+1, h+cnt) - (h+1);
for(i = 1; i <= cnt; i++)
mp[h[i]] = i;
//初始化
memset(head, -1, sizeof(head));
tol = 0;
start = 0; end = cnt + 1; N = end;
add(start, 1, k, 0);
add(cnt, end, k, 0);
for(i = 1; i < cnt; i++)
add(i, i+1, INF, 0);
for(i = 0; i < n; i++)
add(mp[l[i]], mp[r[i]], 1, -w[i]);
mincostmaxflow();
printf("%dn", -mincost); //结果取负
}
return 0;
}
poj 1459 Power Network(最大流 dinic算法 || sap算法 模板)
2015/01/20 | ACM算法学习 | bfshm | 暂无评论 | 1049 views
题目链接
之前做过这个题,这次做是为了整理模板。
Power Network
Time Limit: 2000MS Memory Limit: 32768K
Total Submissions: 23735 Accepted: 12406
Description
A power network consists of nodes (power stations, consumers and dispatchers) connected by power transport lines. A node u may be supplied with an amount s(u) >= 0 of power, may produce an amount 0 <= p(u) <= pmax(u) of power, may consume an amount 0 <= c(u) <= min(s(u),cmax(u)) of power, and may deliver an amount d(u)=s(u)+p(u)-c(u) of power. The following restrictions apply: c(u)=0 for any power station, p(u)=0 for any consumer, and p(u)=c(u)=0 for any dispatcher. There is at most one power transport line (u,v) from a node u to a node v in the net; it transports an amount 0 <= l(u,v) <= lmax(u,v) of power delivered by u to v. Let Con=Σuc(u) be the power consumed in the net. The problem is to compute the maximum value of Con.
An example is in figure 1. The label x/y of power station u shows that p(u)=x and pmax(u)=y. The label x/y of consumer u shows that c(u)=x and cmax(u)=y. The label x/y of power transport line (u,v) shows that l(u,v)=x and lmax(u,v)=y. The power consumed is Con=6. Notice that there are other possible states of the network but the value of Con cannot exceed 6.
Input
There are several data sets in the input. Each data set encodes a power network. It starts with four integers: 0 <= n <= 100 (nodes), 0 <= np <= n (power stations), 0 <= nc <= n (consumers), and 0 <= m <= n^2 (power transport lines). Follow m data triplets (u,v)z, where u and v are node identifiers (starting from 0) and 0 <= z <= 1000 is the value of lmax(u,v). Follow np doublets (u)z, where u is the identifier of a power station and 0 <= z <= 10000 is the value of pmax(u). The data set ends with nc doublets (u)z, where u is the identifier of a consumer and 0 <= z <= 10000 is the value of cmax(u). All input numbers are integers. Except the (u,v)z triplets and the (u)z doublets, which do not contain white spaces, white spaces can occur freely in input. Input data terminate with an end of file and are correct.
Output
For each data set from the input, the program prints on the standard output the maximum amount of power that can be consumed in the corresponding network. Each result has an integral value and is printed from the beginning of a separate line.
Sample Input题意:
总共有n个节点,其中有发电站np个、用户nc个和调度器n-np-nc个三种节点,每个发电站有一个最大发电量,每个用户有个最大接受电量,现在有m条有向边,边有一个最大的流量代表,最多可以流出这么多电,现在从发电站发电到用户,问最多可以发多少电。
分析:
很经典的多源多汇题目。
将发电站看成源点,用户看成汇点,这样求最大流就可以了,不过因为有多个源点和汇点,所以加一个超级源点指向所有的发电站,流量为无限大,加一个超级汇点让所有的用户指向他,流量也为无限大,这样,从超级源点到超级汇点求最大流即可。
用的bin神的模板,按照自己的风格稍微改了一下, bin神该题链接
dinic方法:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e2 + 10; //点
const int maxm = 2e4 + 300; //边
const int INF = 1<<28;
using namespace std;
//dep是点的层次
int n, tol, dep[maxn], head[maxn];
struct node
{
int from, to, next;
int cap;
}edge[maxm];
void init()
{
tol = 0;
memset(head, -1, sizeof(head));
}
void add(int u, int v, int w)
{
edge[tol].from = u;
edge[tol].to = v;
edge[tol].cap = w;
edge[tol].next = head[u];
head[u] = tol++;
edge[tol].from = v;
edge[tol].to = u;
edge[tol].cap = 0;
edge[tol].next = head[v];
head[v] = tol++;
}
int bfs(int start, int end)
{
int que[maxn];
int front, rear;
front = rear = 0;
memset(dep, -1, sizeof(dep));
que[rear++] = start;
dep[start] = 0;
while(front != rear)
{
int u = que[front++];
if(front == maxn) front = 0;
for(int i = head[u]; i!=-1; i=edge[i].next)
{
int v=edge[i].to;
if(edge[i].cap>0 && dep[v]==-1)
{
dep[v]=dep[u]+1;
que[rear++]=v;
if(rear >= maxn)rear=0;
if(v == end)return 1;
}
}
}
return 0;
}
int dinic(int start,int end)
{
int res=0;
int top;
int stack[maxn]; //栈,存储当前增广路
int cur[maxn]; //存储当前点的后继
while(bfs(start,end))
{
memcpy(cur, head, sizeof(head));
int u = start;
top = 0;
while(1)
{
if(u == end)
{
int min = INF;
int loc;
for(int i = 0;i < top;i++)
if(min > edge[stack[i]].cap)
{
min = edge[stack[i]].cap;
loc = i;
}
for(int i = 0;i < top;i ++)
{
edge[stack[i]].cap -= min;
edge[stack[i]^1].cap += min;
}
res += min;
top = loc;
u = edge[stack[top]].from;
}
for(int i = cur[u]; i!=-1; cur[u]=i=edge[i].next)
if(edge[i].cap!=0 && dep[u]+1==dep[edge[i].to])
break;
if(cur[u] != -1)
{
stack[top++] = cur[u];
u = edge[cur[u]].to;
}
else
{
if(top == 0) break;
dep[u] = -1;
u = edge[stack[--top]].from;
}
}
}
return res;
}
int main()
{
int start, end, np, nc, m;
int u, v, w, ans;
while(~scanf("%d%d%d%d", &n, &np, &nc, &m))
{
init();
//以下是输入的处理
while(m--)
{
while(getchar()!='(');
scanf("%d,%d)%d", &u, &v, &w);
u ++; v ++;
add(u, v, w);
}
while(np--)
{
while(getchar()!='(');
scanf("%d)%d", &v, &w);
v ++;
add(0, v, w);
}
while(nc--)
{
while(getchar()!='(');
scanf("%d)%d", &u, &w);
u ++;
add(u, n+1, w);
}
start = 0; end = n+1; //源点和汇点
ans = dinic(start, end);
printf("%dn", ans);
}
return 0;
}
sap方法:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e2 + 10; //点
const int maxm = 2e4 + 300; //边
const int INF = 1<<28;
using namespace std;
//dep是点的层次
int tol, dep[maxn], head[maxn], gap[maxn], cur[maxn]; //gap[x]=y :说明残留网络中dep[i]==x的个数为y
struct node
{
int to, next, cap, flow;
}edge[maxm];
void init()
{
tol = 0;
memset(head, -1, sizeof(head));
}
//加边,单向图三个参数,双向图四个参数
void add(int u, int v, int w, int rw = 0)
{
edge[tol].to = v;
edge[tol].cap = w;
edge[tol].flow = 0;
edge[tol].next = head[u];
head[u] = tol++;
edge[tol].to = u;
edge[tol].cap = rw;
edge[tol].flow = 0;
edge[tol].next = head[v];
head[v] = tol++;
}
int Q[maxn];
void bfs(int start, int end)
{
memset(dep, -1, sizeof(dep));
memset(gap, 0, sizeof(gap));
gap[0] = 1;
int front = 0, rear = 0;
dep[end] = 0;
Q[rear++] = end;
while(front != rear)
{
int u = Q[front++];
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].to;
if(dep[v] != -1)continue;
Q[rear++] = v;
dep[v] = dep[u] + 1;
gap[dep[v]]++;
}
}
}
//起点,终点,点的总数
//点的编号没有影响,只要输入点的总数
int S[maxn]; //栈,存储当前增广路
int sap(int start, int end, int N)
{
bfs(start, end);
memcpy(cur, head, sizeof(head)); //存储当前点的后继
int top = 0;
int u = start;
int ans = 0;
while(dep[start] < N)
{
if(u == end)
{
int Min = INF;
int inser;
for(int i = 0; i < top; i++)
if(Min > edge[S[i]].cap - edge[S[i]].flow)
{
Min = edge[S[i]].cap - edge[S[i]].flow;
inser = i;
}
for(int i = 0; i < top; i++)
{
edge[S[i]].flow += Min;
edge[S[i]^1].flow -= Min;
}
ans += Min;
top = inser;
u = edge[S[top]^1].to;
continue;
}
bool flag = false;
int v;
for(int i = cur[u]; i != -1; i = edge[i].next)
{
v = edge[i].to;
if(edge[i].cap - edge[i].flow && dep[v]+1 == dep[u])
{
flag = true;
cur[u] = i;
break;
}
}
if(flag)
{
S[top++] = cur[u];
u = v;
continue;
}
int Min = N;
for(int i = head[u]; i != -1; i = edge[i].next)
if(edge[i].cap - edge[i].flow && dep[edge[i].to] < Min)
{
Min = dep[edge[i].to];
cur[u] = i;
}
gap[dep[u]]--;
if(!gap[dep[u]])return ans;
dep[u] = Min + 1;
gap[dep[u]]++;
if(u != start) u = edge[S[--top]^1].to;
}
return ans;
}
int main()
{
int start, end, np, nc, m, n;
int u, v, w, ans;
while(~scanf("%d%d%d%d", &n, &np, &nc, &m))
{
init();
//以下是输入的处理
while(m--)
{
while(getchar()!='(');
scanf("%d,%d)%d", &u, &v, &w);
u ++;
v ++;
add(u, v, w, 0);
}
while(np--)
{
while(getchar()!='(');
scanf("%d)%d", &v, &w);
v ++;
add(0, v, w, 0);
}
while(nc--)
{
while(getchar()!='(');
scanf("%d)%d", &u, &w);
u ++;
add(u, n+1, w, 0);
}
start = 0; end = n+1; //源点和汇点
n += 2; //用sap模板的时候要注意,n是总的点数,所以要加上。
ans = sap(start, end, n);
printf("%dn", ans);
}
return 0;
}
hdu 1281 棋盘游戏(二分匹配 匈牙利算法)
2015/01/19 | ACM算法学习 | bfshm | 暂无评论 | 788 views
题目链接
棋盘游戏
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)
Total Submission(s): 2591 Accepted Submission(s): 1510
Problem Description
小希和Gardon在玩一个游戏:对一个N*M的棋盘,在格子里放尽量多的一些国际象棋里面的“车”,并且使得他们不能互相攻击,这当然很简单,但是Gardon限制了只有某些格子才可以放,小希还是很轻松的解决了这个问题(见下图)注意不能放车的地方不影响车的互相攻击。
所以现在Gardon想让小希来解决一个更难的问题,在保证尽量多的“车”的前提下,棋盘里有些格子是可以避开的,也就是说,不在这些格子上放车,也可以保证尽量多的“车”被放下。但是某些格子若不放子,就无法保证放尽量多的“车”,这样的格子被称做重要点。Gardon想让小希算出有多少个这样的重要点,你能解决这个问题么?
Input
输入包含多组数据,
第一行有三个数N、M、K(1<N,M<=100 1<K<=N*M),表示了棋盘的高、宽,以及可以放“车”的格子数目。接下来的K行描述了所有格子的信息:每行两个数X和Y,表示了这个格子在棋盘中的位置。
Output
对输入的每组数据,按照如下格式输出:
Board T have C important blanks for L chessmen.
Sample Input
3 3 4
1 2
1 3
2 1
2 2
3 3 4
1 2
1 3
2 1
3 2Sample Output
Board 1 have 0 important blanks for 2 chessmen.
Board 2 have 3 important blanks for 3 chessmen.
分析:
对N个可以放棋子的点(X1,Y1),(x2,Y2)……(Xn,Yn);我们把它竖着排看看~(当然X1可以对多个点~)
X1 Y1
X2 Y2
X3 Y3
…..
Xn Yn
可以发现:可以根据X坐标与Y坐标把这些点转换为二分图!
首先:只有左边的点与右边的点有关系
其次:符合二分图的最大匹配特性,可以看到如果选择了(X1,Y1)这个点,那么X1与Y1都不能与其他点匹配了,不然的话棋子会互相攻击!
最后:找关键点,只要枚举每条边,删了,看看最大匹配有没有减小,减小了就是关键点(边)了。
分析转载自:http://blog.csdn.net/me4546/article/details/6358533
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e2 + 10;
using namespace std;
int n, m, g[maxn][maxn], vis[maxn], linker[maxn];
bool dfs(int u)
{
int i;
for(i = 1; i <= m; i++) //枚举列
if(g[u][i] && vis[i]==0)
{
vis[i] = 1;
if(linker[i]==-1 || dfs(linker[i]))
{
linker[i] = u;
return true;
}
}
return false;
}
int hungary()
{
int res = 0, i;
memset(linker, -1, sizeof(linker));
for(i = 1; i <= n; i++) //枚举行
{
memset(vis, 0, sizeof(vis));
if(dfs(i)) res ++;
}
return res;
}
int main()
{
int k, i, j, u, v;
int ca = 1, ans, cnt;
while(~scanf("%d%d%d", &n, &m, &k))
{
cnt = 0;
memset(g, 0, sizeof(g));
while(k--)
{
scanf("%d%d", &u, &v);
g[u][v] = 1;
}
ans = hungary(); //找出最多能放多少
for(i = 1; i <= n; i++)
for(j = 1; j <= m; j++)
if(g[i][j])
{
g[i][j] = 0;
if(ans > hungary()) //逐渐删点,删过一个点之后求最大匹配,如果返回值小于原最大匹配则 cnt++,说明当前是关键点
cnt ++;
g[i][j] = 1;
}
printf("Board %d have %d important blanks for %d chessmen.n", ca++, cnt, ans);
}
return 0;
}
poj 1236 Network of Schools(强连通分量 Tarjan 缩点)
2015/01/18 | ACM算法学习 | bfshm | 暂无评论 | 2229 views
题目链接
Network of Schools
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 11852 Accepted: 4714
Description
A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the “receiving schools”). Note that if B is in the distribution list of school A, then A does not necessarily appear in the list of school B
You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school.
Input
The first line contains an integer N: the number of schools in the network (2 <= N <= 100). The schools are identified by the first N positive integers. Each of the next N lines describes a list of receivers. The line i+1 contains the identifiers of the receivers of school i. Each list ends with a 0. An empty list contains a 0 alone in the line.
Output
Your program should write two lines to the standard output. The first line should contain one positive integer: the solution of subtask A. The second line should contain the solution of subtask B.
Sample Input
题意:
有N个学校,给出第i个学校能向那些学校发软件。 从每个学校都能从一个单向网络到另外一个学校,两个问题
1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。
2:至少需要添加几条边,使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。
即求 添加多少边可成为完全连通图。
分析:
首先找连通分量,然后看连通分量的入度为0点的总数,出度为0点的总数,那么问要向多少学校发放软件,就是入度为零的个数,这样才能保证所有点能够找到
然后第二问添加多少条边可以得到使整个图达到一个强连通分量,答案是入度为0的个数和出度为0的个数中最大的
将这个图的所有子树找出来,然后将一棵子树的叶子结点(出度为0)连到另外一棵子树的根结点上(入度为0),这样将所有的叶子结点和根节点全部消掉之后,就可以得到一整个强连通分量,看最少多少条边,这样就是看叶子结点和根节点哪个多,即出度为0和入度为0哪个多
建图,运行tarjan求强连通分量并且缩点,然后统计缩点后的DAG。
//indeg[]入度, outdeg[]出度。
//其他的和上一篇双连通分量的差不多,tarjan函数也差不多
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e2 + 10;
using namespace std;
struct node
{
int v, ne;
}edge[maxn*maxn];
int head[maxn], low[maxn], dfn[maxn], belong[maxn];
int indeg[maxn], outdeg[maxn], st[maxn], inst[maxn];
int cnt, index, top, b_cnt, n;
void add(int u, int v)
{
edge[cnt].v = v;
edge[cnt].ne = head[u];
head[u] = cnt++;
}
void Tarjan(int u)
{
int i, v;
low[u] = dfn[u] = ++index;
st[++top] = u;
inst[u] = 1;
for(i = head[u]; i != -1; i = edge[i].ne)
{
v = edge[i].v;
if(!dfn[v])
{
Tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(inst[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
b_cnt++;
while(1)
{
v = st[top--];
inst[v] = 0;
belong[v] = b_cnt;
if(v == u)
break;
}
}
}
int main()
{
int i, j, a, in, out, v;
while(~scanf("%d", &n))
{
cnt = index = top = b_cnt = 0;
in = out = 0;
memset(head, -1, sizeof(head));
memset(low, 0, sizeof(low));
memset(dfn, 0, sizeof(dfn));
memset(inst, 0, sizeof(inst));
memset(indeg, 0, sizeof(indeg));
memset(outdeg, 0, sizeof(outdeg));
for(i = 1; i <= n; i++)
while(~scanf("%d", &a) && a)
add(i, a); //有向边,加边
for(i = 1; i <= n; i++)
if(!dfn[i])
Tarjan(i);
for(i = 1; i <= n; i++)
for(j = head[i]; j != -1; j = edge[j].ne)
{
v = edge[j].v;
if(belong[i] != belong[v])
{
indeg[belong[v]] ++;
outdeg[belong[i]] ++;
}
}
for(i = 1; i <= b_cnt; i++)
{
if(!indeg[i]) in ++;
if(!outdeg[i]) out ++;
}
if(b_cnt == 1) printf("1n0n");
else
printf("%dn%dn", in, max(in, out));
}
return 0;
}
poj 3177 Redundant Paths (双连通分量 Tarjan,边双连通,缩点)
2015/01/17 | ACM算法学习 | bfshm | 暂无评论 | 1149 views
题目链接
Redundant Paths
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 9604 Accepted: 4106
Description
In order to get from one of the F (1 <= F <= 5,000) grazing fields (which are numbered 1..F) to another field, Bessie and the rest of the herd are forced to cross near the Tree of Rotten Apples. The cows are now tired of often being forced to take a particular path and want to build some new paths so that they will always have a choice of at least two separate routes between any pair of fields. They currently have at least one route between each pair of fields and want to have at least two. Of course, they can only travel on Official Paths when they move from one field to another.Given a description of the current set of R (F-1 <= R <= 10,000) paths that each connect exactly two different fields, determine the minimum number of new paths (each of which connects exactly two fields) that must be built so that there are at least two separate routes between any pair of fields. Routes are considered separate if they use none of the same paths, even if they visit the same intermediate field along the way.There might already be more than one paths between the same pair of fields, and you may also build a new path that connects the same fields as some other path.
Input
Line 1: Two space-separated integers: F and RLines 2..R+1: Each line contains two space-separated integers which are the fields at the endpoints of some path.
Output
Line 1: A single integer that is the number of new paths that must be built.
题意:
有n个牧场,Bessie 要从一个牧场到另一个牧场,要求至少要有2条独立的路可以走。现已有m条路,求至少要新建多少条路,使得任何两个牧场之间至少有两条独立的路。两条独立的路是指:没有公共边的路,但可以经过同一个中间顶点。
分析:
这个题的题意可以总结为 添加多少条边可成为双向连通图。
解法是 把割边分开的不同分量缩点构树,看入度。
在同一个边双连通分量中,任意两点都有至少两条独立路可达,所以同一个边双连通分量里的所有点可以看做同一个点。
缩点后,新图是一棵树,树的边就是原无向图的桥。
现在问题转化为:在树中至少添加多少条边能使图变为双连通图。
结论:添加边数=(树中度为1的节点数+1)/2
具体方法为,首先把两个最近公共祖先最远的两个叶节点之间连接一条边,这样可以把这两个点到祖先的路径上所有点收缩到一起,因为一个形成的环一定是双连通的。然后再找两个最近公共祖先最远的两个叶节点,这样一对一对找完,恰好是(leaf+1)/2次,把所有点收缩到了一起。
其实求边双连通分量和求强连通分量差不多,每次访问点的时候将其入栈,当low[u]==dfn[u]时就说明找到了一个连通的块,则栈内的所有点都属于同一个边双连通分量,因为无向图要见反向边,所以在求边双连通分量的时候,遇到反向边跳过就行了
Tarjan算法是基于对图深度优先搜索的算法,每个强连通分量为搜索树中的一棵子树。搜索时,把当前搜索树中未处理的节点加入一个堆栈,回溯时可以判断栈顶到栈中的节点是否为一个强连通分量。
当DFN(u)=Low(u)时,以u为根的搜索子树上所有节点是一个强连通分量。
//DFN(u)为节点u搜索的次序编号(时间戳),
//Low(u)为u或u的子树能够追溯到的最早的栈中节点的次序号。
//deg[]记录每个节点的度
//st[]是栈,inst[]判断是否在栈中
//belong[]用来标记是否属于父节点,用来计算度
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <queue>
#include <algorithm>
#include <vector>
#define LL __int64
const int maxn = 5e3+10;
const int e_num = 1e4+10;
const int INF = 1<<28;
using namespace std;
struct node
{
int v, ne;
}edge[2*e_num];
int head[maxn], low[maxn], dfn[maxn], belong[maxn];
int deg[maxn], st[e_num], inst[e_num];
int cnt, index, top, b_cnt;
void add(int u, int v)
{
edge[cnt].v = v;
edge[cnt].ne = head[u];
head[u] = cnt++;
}
void Tarjan(int u, int fa)
{
int i, v;
low[u] = dfn[u] = ++index;
st[++top] = u;
inst[u] = 1;
for(i = head[u]; i != -1; i = edge[i].ne)
{
v = edge[i].v;
if(i == (fa^1))
continue;
if(!dfn[v])
{
Tarjan(v, i);
low[u] = min(low[u], low[v]);
}
else if(inst[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
b_cnt++;
while(1)
{
v = st[top--];
inst[v] = 0;
belong[v] = b_cnt;
if(v == u)
break;
}
}
}
int main()
{
int n, m, u, v, i, j;
while(~scanf("%d%d",&n,&m))
{
cnt = index = top = b_cnt =0;
memset(head,-1,sizeof(head));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(inst,0,sizeof(inst));
memset(deg,0,sizeof(deg));
for(i = 0; i < m; i++)
{
scanf("%d%d", &u, &v);
add(u, v);
add(v, u);
}
for(i = 1; i <= n; i++)
if(!dfn[i])
Tarjan(1, -1);
for(i = 1; i <= n; i++)
for(j = head[i]; j != -1; j = edge[j].ne)
{
v = edge[j].v;
if(belong[i] != belong[v])
deg[belong[i]]++;
}
int sum = 0;
for(i = 1; i <= n; i++)
if(deg[i] == 1)
sum++;
int ans = (sum+1)/2;
printf("%dn", ans);
}
return 0;
}
poj 3169 Layout(差分约束 spfa|bellman_ford)
2015/01/17 | ACM算法学习 | bfshm | 暂无评论 | 885 views
题目链接
Layout
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 7602 Accepted: 3648
Description
Like everyone else, cows like to stand close to their friends when queuing for feed. FJ has N (2 <= N <= 1,000) cows numbered 1..N standing along a straight line waiting for feed. The cows are standing in the same order as they are numbered, and since they can be rather pushy, it is possible that two or more cows can line up at exactly the same location (that is, if we think of each cow as being located at some coordinate on a number line, then it is possible for two or more cows to share the same coordinate).Some cows like each other and want to be within a certain distance of each other in line. Some really dislike each other and want to be separated by at least a certain distance. A list of ML (1 <= ML <= 10,000) constraints describes which cows like each other and the maximum distance by which they may be separated; a subsequent list of MD constraints (1 <= MD <= 10,000) tells which cows dislike each other and the minimum distance by which they must be separated.Your job is to compute, if possible, the maximum possible distance between cow 1 and cow N that satisfies the distance constraints.
Input
Line 1: Three space-separated integers: N, ML, and MD.Lines 2..ML+1: Each line contains three space-separated positive integers: A, B, and D, with 1 <= A < B <= N. Cows A and B must be at most D (1 <= D <= 1,000,000) apart.Lines ML+2..ML+MD+1: Each line contains three space-separated positive integers: A, B, and D, with 1 <= A < B <= N. Cows A and B must be at least D (1 <= D <= 1,000,000) apart.
Output
Line 1: A single integer. If no line-up is possible, output -1. If cows 1 and N can be arbitrarily far apart, output -2. Otherwise output the greatest possible distance between cows 1 and N.
题意:
当排队等候喂食时,奶牛喜欢和它们的朋友站得靠近些。FJ有N(2<=N<=1000)头奶牛,编号从1到N,沿一条直线站着等候喂食。奶牛排在队伍中的顺序和它们的编号是相同的。因为奶牛相当苗条,所以可能有两头或者更多奶牛站在同一位置上。即使说,如果我们想象奶牛是站在一条数轴上的话,允许有两头或更多奶牛拥有相同的横坐标。
一些奶牛相互间存有好感,它们希望两者之间的距离不超过一个给定的数L。另一方面,一些奶牛相互间非常反感,它们希望两者间的距离不小于一个给定的数D。给出ML条关于两头奶牛间有好感的描述,再给出MD条关于两头奶牛间存有反感的描述。(1<=ML,MD<=10000,1<=L,D<=1000000)
你的工作是:如果不存在满足要求的方案,输出-1;如果1号奶牛和N号
奶牛间的距离可以任意大,输出-2;否则,计算出在满足所有要求的情况下,1号奶牛和N号奶牛间可能的最大距离。
分析:
贴一个链接:poj3169 layout差分约束的证明。。。
因为在队伍中的顺序必须和编号相同,所以对于任意I号奶牛,1<=I<N,在距离上应该满足:
D[I+1] – D[I] >= 0
对于每个好感的描述(i,j,k),假设i<=j,体现到距离上的要求就是:
D[j] – D[I] <= k
对于每个反感的描述(i,j,k),假设i<=j,体现到距离上的要求就是:
D[j] – D[I] >= k
具体步骤是:
作有向图G=(V,E),V={ v1,v2,v3,…,vn},E={e1,e2,e3,…},对于相邻两点i和(i+1),对应的顶点vi+1向vi引一条边,费用为0;对于每组好感描述(ai,bi,di),我们假设有ai<bi,否则ai和bi交换,则顶点vai向vbi引一条边,费用为di;对于每组反感描述(ai,bi,di),我们假设有ai<bi,否则ai和bi交换,则顶点vbi向vai引一条边,费用为-di。
于是问题变为在G中求v1到其它所有顶点的最短路。我们证明若G中无负权回路,则问题有解,即存在满足条件的数列,若G中有负权回路,则问题无解,即不存在满足条件的数列。
spfa方法:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <queue>
#include <algorithm>
#include <vector>
#define LL __int64
const int maxn = 1e3+10;
const int INF = 1<<28;
using namespace std;
int cnt, head[maxn], d[maxn], vis[maxn], n, num[maxn];//num数组用来记录每个点进栈的次数
queue<int>q;
struct node
{
int u, v, w, next;
}edge[2*10*maxn]; //边数
void add(int u, int v, int w)
{
edge[cnt].u=u;
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].next=head[u];
head[u]=cnt++;
};
bool spfa(int s)
{
int i, u, v;
for(i = 1; i <= n; i++)
d[i] = INF;
d[s] = 0;
q.push(s);
num[s]++;
vis[s] = 1;
while(!q.empty())
{
u = q.front();
q.pop();
vis[u] = 0;
for(i = head[u]; i != -1; i = edge[i].next)
{
v=edge[i].v;
if(d[u] + edge[i].w < d[v])
{
d[v] = d[u] + edge[i].w;
if(!vis[v])
{
vis[v] = 1;
q.push(v);
num[v]++;
if(num[v]>n) return false;//大于n次说明有负环
}
}
}
}
return true;
};
int main()
{
int ml, md, i;
int a, b, w;
while(~scanf("%d%d%d", &n, &ml, &md))
{
cnt = 0;
memset(head, -1, sizeof(head));
memset(vis, 0, sizeof(vis));
memset(num, 0, sizeof(num));
for(i = 0; i < ml; i++)
{
scanf("%d%d%d", &a, &b, &w);
add(a, b, w);
}
for(i = 0; i < md; i++)
{
scanf("%d%d%d", &a, &b, &w);
add(b, a, -w);
}
if(!spfa(1))
cout<<-1<<endl;
else if(d[n] >= INF)
cout<<-2<<endl;
else
cout<<d[n]<<endl;
}
}
bellman_ford方法:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <queue>
#include <algorithm>
#include <vector>
#define LL __int64
const int maxn = 1e3+10;
const int INF = 1<<28;
using namespace std;
int cnt, head[maxn], d[maxn], n;
queue<int>q;
struct node
{
int u, v, w, next;
}edge[2*10*maxn];
void add(int u, int v, int w)
{
edge[cnt].u = u;
edge[cnt].v = v;
edge[cnt++].w = w;
}
bool bellman_ford(int s)
{
for(int i = 1; i <= n; i++)
d[i] = INF;
d[s] = 0;
for(int i = 0; i < n-1; i++)
{
for(int j = 0; j < cnt; j++)
{
int u = edge[j].u;
int v = edge[j].v;
int w = edge[j].w;
if(d[v] > d[u] + w)
{
d[v] = d[u] + w;
}
}
}
for(int j = 0; j < cnt; j++)
{
int u = edge[j].u;
int v = edge[j].v;
int w = edge[j].w;
if(d[v] > d[u] + w)
{
return false;
}
}
return true;
}
int main()
{
int ml, md, i;
int a, b, w;
while(~scanf("%d%d%d", &n, &ml, &md))
{
cnt = 0;
memset(head, -1, sizeof(head));
for(i = 0; i < ml; i++)
{
scanf("%d%d%d", &a, &b, &w);
add(a, b, w);
}
for(i = 0; i < md; i++)
{
scanf("%d%d%d", &a, &b, &w);
add(b, a, -w);
}
if(!bellman_ford(1))
cout<<-1<<endl;
else if(d[n] >= INF)
cout<<-2<<endl;
else
cout<<d[n]<<endl;
}
}
hdu 5155 Harry And Magic Box( dp )
2015/01/15 | ACM算法学习 | bfshm | 暂无评论 | 753 views
题目链接
Harry And Magic Box
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 325 Accepted Submission(s): 156
Problem Description
One day, Harry got a magical box. The box is made of n*m grids. There are sparking jewel in some grids. But the top and bottom of the box is locked by amazing magic, so Harry can’t see the inside from the top or bottom. However, four sides of the box are transparent, so Harry can see the inside from the four sides. Seeing from the left of the box, Harry finds each row is shining(it means each row has at least one jewel). And seeing from the front of the box, each column is shining(it means each column has at least one jewel). Harry wants to know how many kinds of jewel’s distribution are there in the box.And the answer may be too large, you should output the answer mod 1000000007.
Input
There are several test cases.
For each test case,there are two integers n and m indicating the size of the box. 0≤n,m≤50.
Output
For each test case, just output one line that contains an integer indicating the answer.
Sample Input
1 1
2 2
2 3
Sample Output
1
7
25
题意:
这个盒子由n*m个格子组成,有一些格子里会有闪闪发光的宝石。但是盒子的顶部和底部都被神奇的魔法封印着,所以哈利没办法从顶部和底部看到盒子的内部。然而,盒子的四边都是透明的,哈利可以从盒子的四边看到里面的情况。哈利发现,这个神奇的盒子,从左边看过去,每一行都闪烁着光芒,从前面看过去,每一列也都闪烁着光芒。哈利想知道,盒子里的宝石有多少种分布情况。答案有可能很大,所以输出答案对1000000007取模。
分析:
dp题,我们一行一行的考虑。dp[i][j],表示前i行,都满足了每一行至少有一个宝石的条件,而只有j列满足了有宝石的条件的情况有多少种。枚举第i+1行放的宝石数k,这k个当中有t个是放在没有宝石的列上的,那么我们可以得到转移方程:
dp[i+1][j+t]+=dp[i][j]*c[m-j][t]*c[j][k-t],其中c[x][y],意为在x个不同元素中无序地选出y个元素的所有组合的个数。
比赛的时候想和之前的放棋子求每行每列都有棋子的放的次数的期望,有点像。
但是,考虑的不对,之前的题求期望,更注重随机性。
而这道题注重的是统计方法数。
注意下面的tmp,需要先取余,不然的话三个连乘会超LL。
代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <algorithm>
#define LL __int64
const int maxn = 1e2 + 10;
using namespace std;
const int mo = 1000000007;
LL c[maxn][maxn], d[maxn][maxn];
void init()
{
LL i, j;
for(i = 0; i < 55; i++)
c[i][0] = c[i][i] = 1;
for(i = 1; i < 55; i++)
for(j = 1; j < i; j++)
c[i][j] = (c[i-1][j-1] + c[i-1][j])%mo;
}
int main()
{
LL i, j, k, x, n, m;
init();
while(~scanf("%I64d%I64d", &n, &m))
{
memset(d, 0, sizeof(d));
for(i = 1; i <= m; i++)
d[1][i] = c[m][i];
for(i = 2; i <= n; i++)
for(j = 1; j <= m; j++)
for(k = 1; k <= m; k++)
for(x = 0; x <= k && x<=m-j; x++)
{
LL tmp = (c[m-j][x]*c[j][k-x])%mo;
d[i][j+x] = (d[i][j+x] + d[i-1][j]*tmp)%mo;
}
cout<<d[n][m]<<endl;
}
}

浙公网安备 33010602011771号