Problem One:【Usaco2008 Oct 资格赛】
Description
Farmer John已经决定把水灌到他的n(1<=n<=300)块农田,农田被数字1到n标记。把一块土地进行灌水有两种方法,从其他农田饮水,或者这块土地建造水库。
建造一个水库需要花费wi(1<=wi<=100000),连接两块土地需要花费Pij(1<=pij<=100000,pij=pji,pii=0).
计算Farmer John所需的最少代价。
Input
第一行:一个数n
第二行到第n+1行:第i+1行含有一个数wi
第n+2行到第2n+1行:第n+1+i行有n个被空格分开的数,第j个数代表pij。
Output
第一行:一个单独的数代表最小代价.
Sample Input
4
5
4
4
3
0 2 2 2
2 0 3 3
2 3 0 4
2 3 4 0
Sample Output
9
刚看到这题就像以前做过的题目,还以为是二维的要打状态压缩呢,然而很明显不是。看到有一条链,答案又要求的是一个森林,接着就想到了最小生成树。
我用的是克鲁斯卡尔算法
如果要处理w[i],那么就用0到i连一条w[i]的边就行了。得出来的每一棵树设他有i个点,那就有i-1条边,每棵树都有一个w[i]连进去,所以实际的边与除了w[i]的点得数量是相等的。所以当加进去的边有n条时就退出克鲁斯卡尔。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int i,j,k,l,t,n,m,w[30005],num,gx,gy,ji,x,y;
int f[90005];
long long ans;
struct node{
int a,b,c;
}bian[90005];
bool cmp(node x,node y){return x.c<y.c;}
int gf(int x){
if(f[x]==0) return x;
else return f[x]=gf(f[x]);
}
int main(){
scanf("%d",&n);
fo(i,1,n){
scanf("%d",&w[i]);
bian[++num].a=0;bian[num].b=i;bian[num].c=w[i];
}
fo(i,1,n){
fo(j,1,n){
scanf("%d",&t);
if(i<j)bian[++num].a=i,bian[num].b=j,bian[num].c=t;
}
}
sort(bian+1,bian+num+1,cmp);
fo(i,1,num){
x=bian[i].a;y=bian[i].b;
gx=gf(x);gy=gf(y);
if(gx!=gy){
ji++;
f[gx]=gy;
ans+=bian[i].c;
if(ji==n) break;
}
else continue;
}
printf("%d\n",ans);
}
Problem Two:【NOI2001】
Description
司令部的将军们打算在N*M的网格地图上部署他们的炮兵部队。一个N*M的地图由N行M列组成,地图的每一格可能是山地(用“H” 表示),也可能是平原(用“P”表示).
如果在地图中的灰色所标识的平原上部署一支炮兵部队,则图中的黑色的网格表示它能够攻击到的区域:沿横向左右各两格,沿纵向上下各两格。图上其它白色网格均攻击不到。从图上可见炮兵的攻击范围不受地形的影响。 现在,将军们规划如何部署炮兵部队,在防止误伤的前提下(保证任何两支炮兵部队之间不能互相攻击,即任何一支炮兵部队都不在其他支炮兵部队的攻击范围内),在整个地图区域内最多能够摆放多少我军的炮兵部队。
Input
文件的第一行包含两个由空格分割开的正整数,分别表示N和M; 接下来的N行,每一行含有连续的M个字符(‘P’或者‘H’),中间没有空格。按顺序表 示地图中每一行 的数据。 N≤100;M≤10。
Output
文件仅在第一行包含一个整数K,表示最多能摆放的炮兵部队的数量。
Sample Input
5 4
PHPP
PPHH
PPPP
PHPP
PHHP
Sample Output
6
这是一道很经典的状态压缩DP
设f[i,j,k]当前到第i行,这一行的状态为i,上一行的状态为k
首先预处理f[1]的状态和一个正确状态的所有情况【要满足1001,中间隔两个零,
return (!(x&(x<<1)))&&(!(x&(x<<2)))&&(!(x&(x>>1)))&&(!(x&(x>>2)));及这个与左边和左边的左边不同,与右边和右边的右边不同】
从上上行k和上行j推到这一行l
f[i,l,j]=max(f[i,l,j],f[i-1,j,k]+l中1的个数)
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int i,j,k,l,t,n,m,ans,num,yi,er,num1,p,q;
char ch[1025];
int f[2][1025][1025];
int a[1005],b[1025];
bool pan(int x){
return (!(x&(x<<1)))&&(!(x&(x<<2)))&&(!(x&(x>>1)))&&(!(x&(x>>2)));
}
int shu(int x){
int t=0,l=x;
while(l!=0){
t+=(l&1);
l=l/2;
}
return t;
}
int main(){
scanf("%d%d",&n,&m);
fo(i,1,n){
scanf("%s",ch+1);
fo(j,1,m){
if(ch[j]=='P') a[i]+=(1<<(j-1));
}
}
b[++b[0]]=0;
fo(i,1,(1<<m)-1) if(pan(i)) {
b[++b[0]]=i;
if((i&a[1])==i)f[0][i][0]=shu(i);
}
fo(i,2,n){
q=(p^1);
fo(j,1,b[0]){
fo(k,1,b[0]){
if(f[p][b[j]][b[k]]){
t=(((1<<m)-1-(b[j]|b[k]))&a[i]);
fo(l,1,b[0]){
if((t&b[l])==b[l])f[q][b[l]][b[j]]=max(f[q][b[l]][b[j]],
f[p][b[j]][b[k]]+shu(b[l]));
}
}
}
}
memset(f[p],0,sizeof(f[p]));
p=q;
}
fo(j,1,b[0]){
fo(k,1,b[0]){
ans=max(ans,f[p][b[j]][b[k]]);
}
}
printf("%d\n",ans);
}
Problem Three:
Description
给定一些岛屿和一些连接岛屿的桥梁,大家都知道汉密尔顿路是访问每个岛屿一次的路线,在我们这个地图中,每个岛屿有个正整数的权值,表示这个岛屿的观赏价值。假设一共有N个岛屿,用Vi表示岛屿Ci的价值,汉密尔顿路C1C2….Cn的价值是以下三部分的总和:
(1)所有岛屿的价值之和;
(2)对于路径中相邻的两个岛屿CiCi+1,把两个岛屿的价值之积加到总价值中;
(3)路径中连续三个岛屿CiCi+1Ci+2,如果Ci与Ci+2有桥直接相连,则把这三个岛屿价值之积加到总价值中。
要求计算汉密尔顿路最大的价值以及方案数。
Input
输入第一行是一个整数Q(Q<=20),表示测试数据的数量。每个测试数据第一行输入两个整数N和M,分别表示岛屿数和桥梁数,接下来一行包含N个正整数,第i个数表示Vi,每个数不超过100,最后M行,每行两个数X,Y,表示岛X和岛Y之间有一座桥直接相连,桥是双向的,岛屿编号为1到N(N<=13)
Output
对于每个测试数据,输出一行,两个整数,第一个数表示最大价值,第二个数表示方案数,如果不存在汉密尔顿路径,输出“0 0”
注意:一条路径可以反着走,我们认为这两条路径是同一条路径。
Sample Input
2
3 3
2 2 2
1 2
2 3
3 1
4 6
1 2 3 4
1 2
1 3
1 4
2 3
2 4
3 4
Sample Output
22 3
69 1
设f[i,j,k]为当前经过的岛屿状态为i,上上个连接的点为j,上一个连接的点为k的方案数
那么转移方程很显然:f[i,l,j]=max(f[i][k][l]+v[j]+v[l] * v[j]+v[l] * v[j] * v[k])(i-p为i没有连接k时的状态);g[i,l,j]表示方案数,转移很显然,当f[i][k][l]+v[j]+v[l] * v[j]+v[l] * v[j] * v[k]比f[i,l,j]大,那么g[i,l,j]就被覆盖,如果相等就叠加。
注意处理当n=1时候的情况与开long long(int64)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int i,j,k,l,t,n,m,ll,tt,da,p,q,o;
long long ans,ans1;
int v[100],a[100][100];
int f[10000][20][20],g[10000][20][20];
int main(){
scanf("%d",&tt);
fo(ll,1,tt){
scanf("%d%d",&n,&m);
memset(f,0,sizeof(f));
memset(g,0,sizeof(g));
memset(a,0,sizeof(a));
fo(i,1,n){
scanf("%d",&v[i]);
}
fo(i,1,m){
scanf("%d%d",&k,&l);
a[k][l]=a[l][k]=1;
}
fo(k,1,n){
fo(l,1,n){
if((a[k][l])&&(k!=l)){
f[(1<<(k-1))+(1<<(l-1))][k][l]=
v[k]*v[l]+v[k]+v[l];
g[(1<<(k-1))+(1<<(l-1))][k][l]=1;
}
}
}
da=(1<<n)-1;
fo(i,0,da){
fo(j,1,n){
p=1<<(j-1);
if((i&p)==0){
fo(k,1,n){
fo(l,1,n){
if(f[i][k][l]&&a[l][j]){
o=f[i][k][l]+v[j]+
v[l]*v[j]+v[l]*v[j]*v[k]*a[k][j];
if(o>f[i+p][l][j]){
f[i+p][l][j]=o;
g[i+p][l][j]=g[i][k][l];
}
else if(o==f[i+p][l][j])
g[i+p][l][j]+=g[i][k][l];
}
}
}
}
}
}
ans=0;ans1=0;
fo(i,1,n){
fo(j,1,n){
if(f[da][i][j]>ans){
ans=f[da][i][j];
ans1=g[da][i][j];
}
else if(f[da][i][j]==ans)
ans1+=g[da][i][j];
}
}
if(n==1) ans1=2,ans=v[1];
printf("%lld %lld\n",ans,ans1/2);
}
}
Problem Two:【Usaco 2010 NOV Gold】
Description
Farmer John希望给他的N(1<=N<=100,000)只奶牛拍照片,这样他就可以向他的朋友炫耀他的奶牛.这N只奶牛被标号为1..N.
在照相的那一天,奶牛们排成了一排.其中第i个位置上是标号为c_i(1<=c_i<=N)的奶牛.对于奶牛的站位,Farmer John有他自己的想法.
FJ是这么想的,标号为i(1<=i<=n-1)的奶牛只能站在标号为i+1的奶牛的左边,而标号为N的奶牛只能站在标号为1的奶牛的左边.当然,没有牛可以站在队列中最左边的奶牛的左边了.也就是说,最左边的奶牛编号是随意的.
这些奶牛都非常的饿,急切的希望吃到FJ承诺的在拍照后的大餐,所以FJ想尽快的拍照.奶牛们的方向感非常的不好,所以FJ每一分钟只可以选择相邻的两只奶牛然后让他们交换位置.FJ最小需要多少时间就能使奶牛站成一个可以接受的序列?
比方说一个有5只奶牛的例子,一开始序列是这样的:
左边 右边
3 5 4 2 1
第一分钟,FJ可以交换第二队奶牛(即5和4),交换后的队列:
3 4 5 2 1
第二分钟,FJ交换最右边的一对,序列变成这样:
3 4 5 1 2
这样,只用了2分钟,就是序列变为了一个FJ所希望的序列.
Input
第1行:一个单独的数N
第2到n+1行:第i+1行上的数表示站在第i的位置上的奶牛的编号(即c_i).
Output
一个整数,表示是奶牛的序列变为一个合法的序列的最小花费时间.
Sample Input
5
3
5
4
2
1
Sample Output
2
首先,如果要把序列变为1,2,3,…,n的样子,显然答案为逆序对的个数。
设c[i]为原序列i的位置
设有一个数为a,在原序列中是[…….a…..],变换之后,序列可为[a,a+1,…,n,1,2,…,a-1],原序列换到这里的步数为k1。那么假如要把a放到后面去序列就是[a+1,a+2,…,n,1,2,…,a],原序列换到这里的步数为k2。可以发现中间的a+1到a-1是重复的,要把序列一向前推一位,先把a+1..n先前推一位,再把1..a-1向前推一位,所以从序列一转移到序列二时,发现a的中转站,是a在原序列中的位置。那么把第一个序列的a还原到原序列中的位置是的步数为(c[a]-1),还原成[a+1,a+2,…,n,a,1,2,..,a-1]。从原数组的a位置还原到第二个序列的步数为n-c[a],还原成[a+1,a+2,…,n,1,2,…,a],所以k1——>k2是k2=k1+n-c[a]-(c[a]-1)。因为是要把序列一退回原序列再推到序列二,从a的原来位置开始的,所以是这样k2=k1+n-c[a]-(c[a]-1)。那么从1,2,…,n开始一直推,就可以O(n)得出答案。
逆序对统计可以用线段树,树状数组,不过最好打的还是mergesort(归并排序)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
int i,j,k,l,t,n,m,a[100005],b[100005],c[100005];
long long ans,ni;
void mergesort(int l,int r){
int mid=(l+r)/2,o,i,j;
if(l!=r)mergesort(l,mid),mergesort(mid+1,r);
i=l;j=mid+1;o=i-1;
while(i<=mid&&j<=r){
if(a[i]<a[j]) b[++o]=a[i++];
else b[++o]=a[j++],ni=ni+(mid-i+1);
}
while(i<=mid) b[++o]=a[i++];
while(j<=r) b[++o]=a[j++];
fo(i,l,r)a[i]=b[i];
}
int main(){
scanf("%d",&n);
fo(i,1,n){
scanf("%d",&a[i]);
c[a[i]]=i;
}
mergesort(1,n);
ans=ni;
fo(i,1,n-1){
ni=ni+(n-c[i])-(c[i]-1);
ans=min(ans,ni);
}
printf("%lld",ans);
}
这次比赛还是没有注意long long的问题,还有一些数据特殊值,在状压DP的转移中想的还有点慢。如果要用到状压DP的,那么每个点或值是只出现一次的,而且有一个输入的值范围特别小,这个反应还有点慢,浪费了一点时间。感到c++好坑啊,位运算的优先级极低,让我调了好久。这次在打并查集的时候,把初始值都赋值为0了,然后有一个点就是0…….幸好发现了。所以后初始值还是f[i]=i好一点。