Problem C A Puzzle for Pirates 经典博弈 4
Problem F Anniversary Party 树形DP 11
Problem G Interesting Yang Yui Triangle 高斯函数、组合、进位制 13
Problem A Station Balance
原题:HDU 1607
出题时可能是搞错了,愿意是想出Poj 1986 Distance Queries,这道题的,Poj 1986是LCA,用tarjan算法可以解。
题目意思是,国际空间站有许多centrifuges,每个centrifuges有一些chambers,这些chambers能容纳0,1,2个宇航员,现在s个宇航员你要怎么样分配,使得没有一个chamber有2个宇航员并且IMBALANCE这个值要最小。
IMBALANCE可以参考题目!
没思路,而且还是Special Judge的题目,有点状态压缩的意思。
Problem B Stars 树状数组
原题:HDU 1541
题目意思就是说,给你一张star maps,每个star有自己的level,level是这样计算的:(Let the level of a star be an amount of the stars that are not higher and not to the right of the given star.)统计这个star左下角star的个数,就是这个star的level。现在要你总计图中level从0到N-1的star分别有多少个?
题目输入描述中明确告诉我们,输入的坐标是按Y的升序、如果Y相等,则按X的升序。所以我们发现我们可以完全忽略Y,只要统计小于本身X的star个数,就是level了。现在我们用一个数组a[]统计X坐标为i的个数(增加一个既a[i]++;);同时对每个star计算的得level。
这样对于X坐标为j的star,他得level为:
level=a[0]+a[1]+a[2]+…+a[j]
如果这样计算的话,每个level最坏时间要用O(m),计算n-1个level,这样要用O(mn)的时间复杂度,对于这题的数据而言,这无疑是无法接受的。我们发现,对于计算level,这是个查询区间问题sum[0,j], 如果没有元素的变更(既数组a是不变的),我们完全可以存储sum[0,k](k=0,2,……),然后对任意给定的查找区间[i,j],都可以方便的用ans=sum[1,j]-sum[1,i-1],当然这只是没有元素改变的情况下的比较优化的解法.那么对于有元素变更的问题是否有更高效的方法呢?这让我们想到的树状数组。
树状数组所针对问题:已知数组a[],元素个数为n,现在更改a中的元素,要求得新的a数组中i到j区间内的和(1<=i<=j<=n).
这题是比较简单和明显的树状数组,我们先不讨论如何实现树状数组。只告诉你树状数组现在有2个操作:
(1) add(int i,int val) //将第i个元素更改val
(2) sum(int i) //求前i项和
所以这题我们的伪代码可以如下实现:
for(i=0;i<n;i++)
{
1. sacnf(x,y);
2. cnt[sum(x+1)]++;
3. add(x+1,1);
4.
}
5.print:cnt[0,n-1];
上面因为树状数组是从1开始计数的,而坐标有可能为0,所以我们统一都加1;sum(x+1)就是计算当前star的level值,所以当前level个数加1;add(x+1,1)操作表示对当前元素更改(既+1);最后打印cnt数组。
树状数组的实现及其思想
那么树状数组是如何实现快速对数组更新和区间求和的呢?
关于树状数组的实现和思想,这个再不细说,参考下面资料:
http://baike.baidu.com/view/1420784.htm
http://www.cnblogs.com/yykkciwei/archive/2009/05/08/1452889.html
//============================================================================
// Name : Stars.cpp
// Author :
// Version :
// Copyright : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int MAXN=32010;
int n,c[MAXN],cnt[MAXN];
inline int lowbit(int x)
{
return x&(-x);
}
void add(int i,int val)
{
while(i<MAXN)
{
c[i]+=val;
i+=lowbit(i);
}
}
int sum(int i)
{
int s=0;
while(i>0)
{
s+=c[i];
i-=lowbit(i);
}
return s;
}
int main() {
int x,y;
while(scanf("%d",&n)!=EOF)
{
memset(c,0,sizeof(c));
memset(cnt,0,sizeof(cnt));
for(int i=0;i<n;i++)
{
scanf("%d%d",&x,&y);
cnt[sum(x+1)]++;
add(x+1,1);
}
for(int i=0;i<n;i++)
{
printf("%d\n",cnt[i]);
}
}
return 0;
}
Problem C A Puzzle for Pirates 经典博弈
原题:hdu 1538
海盗分金币问题,很有意思的题目,经典的博弈问题,据说是Microsoft亚洲总裁面试的题目。这种问题的变幻莫测,规则改动一下就是新的问题。本题的题目描述很长,但是规则是一样的。先看看最初的原题:
10名海盗抢得了窖藏的100块金子,并打算瓜分这些战利品。这是一些讲民主的海盗(当然是他们自己特有的民主),他们的习惯 是按下面的方式进行分配:最厉害的一名海盗提出分配方案,然后所有的海盗(包括提出方案者本人)就此方案进行表决。如果50%或更多的海盗赞同此方案,此 方案就获得通过并据此分配战利品。否则提出方案的海盗将被扔到海里,然后下提名最厉害的海盗又重复上述过程。
所有的海盗都乐于看到他们的一位同伙被扔进海里,不过,如果让他们选择的话,他们还是宁可得一笔现金。他们当然也不愿意自己被扔到海里。所有的海盗都是有 理性的,而且知道其他的海盗也是有理性的。此外,没有两名海盗是同等厉害的——这些海盗按照完全由上到下的等级排好了座次,并且每个人都清楚自己和其他所 有人的等级。这些金块不能再分,也不允许几名海盗共有金块,因为任何海盗都不相信他的同伙会遵守关于共享金块的安排。这是一伙每人都只为自己打算的海盗。 最凶的一名海盗应当提出什么样的分配方案才能使他获得最多的金子呢?
为方便起见,我们按照这些海盗的怯懦程度来给他们编号。最怯懦的海盗为1号海盗,次怯懦的海盗为2号海盗,如此类推。这样最厉害的海盗就应当得到最大的编号,而方案的提出就将倒过来从上至下地进行。
分析所有这类策略游戏的奥妙就在于应当从结尾出发倒推回去。游戏结束时,你容易知道何种决策有利而何种决策不利。确定了这一点后,你就可以把它用到倒数第 2次决策上,如此类推。如果从游戏的开头出发进行分析,那是走不了多远的。其原因在于,所有的战略决策都是要确定:“如果我这样做,那么下一个人会怎样做?”
因此在你以下海盗所做的决定对你来说是重要的,而在你之前的海盗所做的决定并不重要,因为你反正对这些决定也无能为力了。
记住了这一点,就可以知道我们的出发点应当是游戏进行到只剩两名海盗——即1号和2号——的时候。这时最厉害的海盗是2号,而他的最佳分配方案是一目了然 的:100块金子全归他一人所有,1号海盗什么也得不到。由于他自己肯定为这个方案投赞成票,这样就占了总数的50%,因此方案获得通过。
现在加上3号海盗。1号海盗知道,如果3号的方案被否决,那么最后将只剩2个海盗,而1号将肯定一无所获——此外,3号也明白1号了解这一形势。因此,只 要3号的分配方案给1号一点甜头使他不至于空手而归,那么不论3号提出什么样的分配方案,1号都将投赞成票。因此3号需要分出尽可能少的一点金子来贿赂1 号海盗,这样就有了下面的分配方案: 3号海盗分得99块金子,2号海盗一无所获,1号海盗得1块金子。
4号海盗的策略也差不多。他需要有50%的支持票,因此同3号一样也需再找一人做同党。他可以给同党的最低贿赂是1块金子,而他可以用这块金子来收买2号 海盗。因为如果4号被否决而3号得以通过,则2号将一文不名。因此,4号的分配方案应是:99块金子归自己,3号一块也得不到,2号得1块金子,1号也是 一块也得不到。
5号海盗的策略稍有不同。他需要收买另两名海盗,因此至少得用2块金子来贿赂,才能使自己的方案得到采纳。他的分配方案应该是:98块金子归自己,1块金子给3号,1块金子给1号。
这一分析过程可以照着上述思路继续进行下去。每个分配方案都是唯一确定的,它可以使提出该方案的海盗获得尽可能多的金子,同时又保证该方案肯定能通过。照 这一模式进行下去,10号海盗提出的方案将是96块金子归他所有,其他编号为偶数的海盗各得1块金子,而编号为奇数的海盗则什么也得不到。这就解决了10 名海盗的分配难题。
Omohundro的贡献是他把这一问题扩大到有500名海盗的情形,即500名海盗瓜分100块金子。显然,类似的规律依然成立——至少是在一定范围内成立。事实上,前面所述的规律直到第200号海盗都成立。 200号海盗的方案将是:从1到199号的所有奇数号的海盗都将一无所获,而从2到198号的所有偶数号海盗将各得1块金子,剩下的1块金子归200号海盗自己所有。
乍看起来,这一论证方法到200号之后将不再适用了,因为201号拿不出更多的金子来收买其他海盗。但是即使分不到金子,201号至少还希望自己不会被扔进海里,因此他可以这样分配:给1到199号的所有奇数号海盗每人1块金子,自己一块也不要。
202号海盗同样别无选择,只能一块金子都不要了——他必须把这100块金子全部用来收买100名海盗,而且这100名海盗还必须是那些按照201号方案将一无所获的人。由于这样的海盗有101名,因此202号的方案将不再是唯一的——贿赂方案有101种。
203号海盗必须获得102张赞成票,但他显然没有足够的金子去收买101名同伙。因此,无论提出什么样的分配方案,他都注定会被扔到海里去喂鱼。不过, 尽管203号命中注定死路一条,但并不是说他在游戏进程中不起任何作用。相反,204号现在知道,203号为了能保住性命,就必须避免由他自己来提出分配 方案这么一种局面,所以无论204号海盗提出什么样的方案,203号都一定会投赞成票。这样204号海盗总算侥幸拣到一条命:他可以得到他自己的1票、 203号的1票、以及另外100名收买的海盗的赞成票,刚好达到保命所需的50%。获得金子的海盗,必属于根据202号方案肯定将一无所获的那101名海 盗之列。
205号海盗的命运又如何呢?他可没有这样走运了。他不能指望203号和204号支持他的方案,因为如果他们投票反对205号方案,就可以幸灾乐祸地看到 205号被扔到海里去喂鱼,而他们自己的性命却仍然能够保全。这样,无论205号海盗提出什么方案都必死无疑。206号海盗也是如此——他肯定可以得到 205号的支持,但这不足以救他一命。类似地,207号海盗需要104张赞成票——除了他收买的100张赞成票以及他自己的1张赞成票之外,他还需3张赞 成票才能免于一死。他可以获得205号和206号的支持,但还差一张票却是无论如何也弄不到了,因此207号海盗的命运也是下海喂鱼。
208号又时来运转了。他需要104张赞成票,而205、206、207号都会支持他,加上他自己一票及收买的100票,他得以过关保命。获得他贿赂的必 属于那些根据204号方案肯定将一无所获的人(候选人包括2到200号中所有偶数号的海盗、以及201、203、204号)。
现在可以看出一条新的、此后将一直有效的规律:那些方案能过关的海盗(他们的分配方案全都是把金子用来收买100名同伙而自己一点都得不到)相隔的距离越 来越远,而在他们之间的海盗则无论提什么样的方案都会被扔进海里——因此为了保命,他们必会投票支持比他们厉害的海盗提出的任何分配方案。得以避免葬身鱼 腹的海盗包括201、202、204、208、216、232、264、328、456号,即其号码等于200加2的某一方幂的海盗。
现在我们来看看哪些海盗是获得贿赂的幸运儿。分配贿赂的方法是不唯一的,其中一种方法是让201号海盗把贿赂分给1到199号的所有奇数编号的海盗,让 202号分给2到200号的所有偶数编号的海盗,然后是让204号贿赂奇数编号的海盗,208号贿赂偶数编号的海盗,如此类推,也就是轮流贿赂奇数编号和 偶数编号的海盗。
结论是:当500名海盗运用最优策略来瓜分金子时,头44名海盗必死无疑,而456号海盗则给从1到199号中所有奇数编号的海盗每人分1块金子,问题就 解决了。由于这些海盗所实行的那种民主制度,他们的事情就搞成了最厉害的一批海盗多半都是下海喂鱼,不过有时他们也会觉得自己很幸运——虽然分不到抢来的 金子,但总可以免于一死。只有最怯懦的200名海盗有可能分得一份脏物,而他们之中又只有一半的人能真正得到一块金子,的确是怯懦者继承财富。
Problem D House man 差分约束
原题:hdu 3440
题目有些难懂,就是给你n栋高度不一房子,房子的编号是从左到右的有序的,你可以左右平移,但不能改变顺序。现在要从最低的那个房子跳起,依次跳到刚比他高那栋,经过n-1跳后,到最高的一栋。每次跳跃的条件是:
(1)高度且序号相邻两个>=1( 因为房子不能重合)
(2)高度相邻两个<=d
求满足条件的跳完最大水平总路程。
既然有不等式组,我们想到的差分约束。
设s(b) - s(a) <= k ,a,b为序号:
b到a的距离 <= k,但是必须定一个规则,a在左边还是b在左边?
这里设a,b是x轴上的点,再设b > a
所以这样的情况下规则就是:s(序号大的) - s(序号小的)才表示b到a之间的距离
(1)位置相邻:
s(i+1) - s(i) >= 1 ---> s(i) - s(i+1) <= -1
(2)高度相邻(排序后):id表示a[i]这间房子的序号
s(max(a[i].id,a[i-1].id))-s(min(a[i].id,a[i-1].id)) <= d
建图后只要这样:
SPFA (min (a[1].id, a[n].id));
printf ("%d\n", dis[max (a[n].id, a[1].id)]);
如果负权值回路,则无解
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
using namespace std;
const int maxn=10010;
const int inf=0x7fffffff;
struct node {
int v;
int len;
};
struct house {
int id;
int high;
};
vector<node> map[maxn];
house h[maxn];
int n, d;
bool cmp(house a, house b) {
return a.high < b.high;
}
int SPFA(int s, int e) {
node p;
int dis[maxn];
bool used[maxn];
int num[maxn];
int u;
queue<int> q;
memset(num, 0, sizeof(num));
memset(used, 0, sizeof(used));
for (int i = 0; i <= n; i++) {
dis[i] = inf;
}
dis[s] = 0;
used[s] = true;
q.push(s);
while (!q.empty()) {
u = q.front();
q.pop();
used[u] = false;
for (int i = 0; i < map[u].size(); i++) {
p = map[u][i];
if (dis[p.v] > dis[u] + p.len) {
dis[p.v] = dis[u] + p.len;
if (!used[p.v]) {
used[p.v] = true;
num[p.v]++;
if (num[p.v] > n) {
return -1;
}
q.push(p.v);
}
}
}
}
return dis[e];
}
void init() {
for (int i = 0; i <= n; i++) {
map[i].clear();
}
}
int main() {
int cas = 1, t;
node tmp;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &d);
init();
for (int i = 1; i <= n; i++) {
scanf("%d", &h[i].high);
h[i].id = i;
}
//s(i) - s(i+1) <= -1
for (int i = 1; i <= n - 1; i++) {
tmp.v = i;
tmp.len = -1;
map[i + 1].push_back(tmp);
}
sort(h + 1, h + 1 + n, cmp);
//s(max(h[i+1].id,h[i].id))-s(min(h[i+1].id,h[i].id)) <= d
for (int i = 1; i <= n - 1; i++) {
tmp.v = max(h[i].id, h[i + 1].id);
tmp.len = d;
map[min(h[i].id, h[i + 1].id)].push_back(tmp);
}
printf("Case %d: %d\n", cas++, SPFA(min(h[1].id, h[n].id), max(h[1].id,
h[n].id)));
}
return 0;
}
Problem E Projects DP
原题:hdu 1684
现在有m个项目和n个人,每一个项目由1,2,3...n个人完成的效率是不一样的,(之前把它理解成每个项目由第1,2...n个人完成不一样来着)还有给你每个人的salary,当项目完成则付报酬reward(i),否则罚款punishment(i),而对应expected profit是这样计算的:(Let p (0 < p < 1) be the probability that a job is finished in time, and let E1 be the profit in that case. Furthermore, let E2 be the (negative) profit in case the job is not finished in time. Then the expected profit for this particular job is p*E1 + (1 - p)*E2.),一个人只能做一个项目(一个项目可以多个人完成)
对于前i个项目,j个人来完成获利,我们定义为dp[i][j]
则有状态转移方程:
dp[i][j]=max(dp[i][j],dp[i-1][j-k]+给第i个项目分配k个人的expected profit)
//============================================================================
// Name : Projects.cpp
// Author :
// Version :
// Copyright : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int MAXN = 110;
const int inf = 0x3f3f3f3f;
//p*E1 + (1 - p)*E2.
int m, n, salary;
int p[MAXN][MAXN];
int cost[MAXN][2];
int dp[MAXN][MAXN];
void Init() {
for (int i = 0; i <= m; i++) {
p[i][0]=p[0][i]=0;
for (int j = 0; j <= n; j++) {
dp[i][j] = -inf;
}
}
dp[0][0]=0;
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
scanf("%d", &m);
scanf("%d", &n);
scanf("%d", &salary);
Init();
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
scanf("%d", &p[i][j]);
}
scanf("%d%d", &cost[i][0], &cost[i][1]);
}
for (int i = 1; i <= m; i++)
{
for (int j = 0; j <= n; j++)
{
for (int k = 0; k <= j; k++)
{
dp[i][j] = max(dp[i][j], dp[i - 1][j - k] + p[i][k] * (cost[i][0]- k * salary) - (100 - p[i][k]) * cost[i][1]);
}
}
}
int ans=-inf;
// printf("ans=%d\n",ans);
int pos=0;
int flg[MAXN];
for(int i=0;i<=n;i++)
{
if(dp[m][i]>ans)
{
ans=dp[m][i];
pos=0;
flg[pos++]=i;
}
else if(dp[m][i]==ans)
{
flg[pos++]=i;
}
}
printf("%d\n",ans);
printf("%d",flg[0]);
for(int i=1;i<pos;i++)
{
printf(" %d",flg[i]);
}
puts("");
}
return 0;
}
Problem F Anniversary Party 树形DP
原题:hdu 1520
学校员工之间的工作关系是一种树形的,校长就是root结点了,现在要参加Party,要求是直接上司和下属关系的员工不能同时参加( the rector does not want both an employee and his or her immediate supervisor to be present. )。同时每个人都用一个conviviality rating值( everyone has some number (rating) attached to him or her. ),现在如何邀请,使得来的人conviviality rating值和最大。
显然是DP,其实树形DP其实就模型结构是树结构而已,跟DP没什么两样。通过分析可以知道这是一个想法非常简单的DP,每个人就取或不取两种状态。还有一个问题就是要找到哪个是root结点!
从叶子结点往根节点迭代,对每个结点x有:
dp[x][取]+={dp[x的孩子][不取]};
dp[x][不取]+={max(dp[x的孩子][取],dp[x的孩子][不取])};
//============================================================================
// Name : Anniversary_party.cpp
// Author :
// Version :
// Copyright : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int MAXN=6100;
const int inf=0x7fffffff;
vector<int>em[MAXN];
int val[MAXN];
int dp[MAXN][2];
bool used[MAXN];
int n;
void Init()
{
for(int i=0;i<=n;i++)
{
em[i].clear();
used[i]=false;
}
}
void Dfs(int root)
{
int len=em[root].size();
if(len==0)
{
dp[root][1]=val[root];
dp[root][0]=0;
return ;
}
for(int i=0;i<len;i++)
{
Dfs(em[root][i]);
dp[root][1]=val[root];
dp[root][0]=0;
for(int j=0;j<len;j++)
{
dp[root][1]+=dp[em[root][j]][0];
dp[root][0]+=max(dp[em[root][j]][0],dp[em[root][j]][1]);
}
}
}
int main() {
while(scanf("%d",&n)!=EOF)
{
Init();
for(int i=1;i<=n;i++)
{
scanf("%d",&val[i]);
}
int son,father;
do{
scanf("%d%d",&son,&father);
if(son==0 && father==0) break;
used[son]=true;
em[father].push_back(son);
}while(true);
int root;
for(int i=1;i<=n;i++)
{
if(!used[i])
{
root=i;
break;
}
}
// printf("root=%d\n",root);
Dfs(root);
printf("%d\n",max(dp[root][0],dp[root][1]));
}
return 0;
}
Problem G Interesting Yang Yui Triangle 高斯函数、组合、进位制
原题:hdu 3304
题意为求杨辉三角第N行中能被素数P整除的数的个数。
可以推公式和证明,用高斯函数来分析,这个我不在行。