JZWC【Day4】题解&总结

来源:互联网 发布:android编程权威指南3 编辑:程序博客网 时间:2024/05/17 04:57

怎么说呢,今天的题不难也不水,很考验人的思维,实为一套好题。但实现难度不高,有了思路很快就能打出来了。

T1 灌水

Description

有n个点,在点i和点j之间连边的费用为p[i,j],把每个点标记为关键点的费用为wi,求所有点都之间或间接的能到达一个关键点的最小费用。

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
输出详解:
把第四个点标为关键,然后把其他的都连向那一个,这样就要花费3+2+2+2=9。

Data Constraint

1<=n<=300
1<=wi<=100000
1<=pij<=100000,pij=pji,pii=0

Solution

很神奇的一道题,我们发现如果没有关键点的限制,那么就只用最小生成树算法就可以了。现在我们多了一个限制,即每棵树上都要有一个关键点。那么我们考虑将第i个点标为关键点的状态转化到边上。加入一个新点,每个点向它连选其为关键点的费用,这样就可以用普通的最小生成树算法来解决了。

Code

#include<cstdio>#include<algorithm>#define fo(i,a,b) for(int i=a;i<=b;i++)#define M 95005#define N 305using namespace std;struct note{    int x,y,z;}a[M];int w[N],x,y,tot,f[N],ans,n;bool cmp(note x,note y) {    return x.z<y.z;}int get(int x) {    if (!f[x]) return x;    return f[x]=get(f[x]);}int main() {    scanf("%d",&n);    fo(i,1,n) {        scanf("%d",&x);a[++tot].x=0;a[tot].y=i;a[tot].z=x;    }    fo(i,1,n)        fo(j,1,n) {            scanf("%d",&x);a[++tot].x=i;a[tot].y=j;a[tot].z=x;        }    sort(a+1,a+tot+1,cmp);    fo(i,1,tot) {        x=get(a[i].x);y=get(a[i].y);        if (x!=y) {            ans+=a[i].z;f[x]=y;            if (--n==0) break;        }    }    printf("%d",ans);}

T2 炮兵阵地

Description

在一个n*m的地图上放炮兵,每个炮兵可以攻击上下左右两格的区域,如图这里写图片描述 每一格有两种状态,P表示可放,H表示不可放,求在所有炮兵互相不能攻击到的情况下最多能放多少炮兵。

Input

  文件的第一行包含两个由空格分割开的正整数,分别表示N和M;   接下来的N行,每一行含有连续的M个字符(‘P’或者‘H’),中间没有空格。按顺序表示地图中每一行的数据。

Output

  文件仅在第一行包含一个整数K,表示最多能摆放的炮兵部队的数量。

Sample Input

5 4
PHPP
PPHH
PPPP
PHPP
PHHP

Sample Output

6

Data Constraint

N<=100,M<=10

Solution

经典状压dp例题,预处理出每一行所有合法的状态,直接转移就好了。

Code

#include<cstdio>#include<cstring>#include<algorithm>#define fo(i,a,b) for(int i=a;i<=b;i++)#define N 105#define M 1025using namespace std;int a[N],f[2][M][M],n,m,mx,b[M],c[M],p,ans,tot;char s[15];bool check(int x) {    return (!(x&(x<<1)))&&(!(x&(x<<2)))&&(!(x&(x>>1)))&&(!(x&(x>>2)));}int cout(int x) {    int z;    for(z=0;x;x>>=1) z+=(x&1);    return z;}int main() {    scanf("%d%d",&n,&m);mx=(1<<m)-1;    fo(i,1,n) {        scanf("%s",s+1);        fo(j,1,m) if (s[j]=='H') a[i]+=1<<(j-1);    }p=1;    fo(i,0,mx)         if (check(i)) {            b[++tot]=i;c[tot]=cout(i);            if (!(i&a[1])) f[0][0][i]=c[tot];        }    fo(i,2,n) {        fo(j,1,tot)            fo(k,1,tot)                if (f[1-p][b[j]][b[k]]) {                   int t=(mx-(b[j]|b[k]))&(mx-a[i]);                   fo(l,1,tot)                       if ((b[l]&t)==b[l])                        f[p][b[k]][b[l]]=max(f[p][b[k]][b[l]],                       f[1-p][b[j]][b[k]]+c[l]);                        }           p=1-p;memset(f[p],0,sizeof(f[p]));    }    fo(i,1,tot)         fo(j,1,tot) ans=max(ans,f[1-p][b[i]][b[j]]);    printf("%d",ans);} 

T3 Islands and Bridges

Description

给出有n个点的一张无向图,每个点有一个价值v[i],求n个点的一个遍历序列,使得
(1)所有点的价值和
(2)序列中相邻两格点的价值之积的和
(3)若一个点和在它上一个点的上一个点有边相连,则有这三个点的价值之积的和
这些值的和最大,求最大值和方案数。

Input

  输入第一行是一个整数Q,表示测试数据的数量。每个测试数据第一行输入两个整数N和M,分别表示点数和边数,接下来一行包含N个正整数,第i个数表示Vi,最后M行,每行两个数X,Y,表示点X和点Y之间有一条边直接相连。

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

Data Constraint

Q<=20,N<=13,Vi<=100

Solution、

又一道状压dp例题,注意细节。

Code

#include<cstdio>#include<cstring>#include<algorithm>#define fo(i,a,b) for(int i=a;i<=b;i++)#define N 14#define M 10000#define ll long longusing namespace std;int ty,n,m,v[N],a[N][N],f[M][N][N],ans,tot,x,y,maxn;ll g[M][N][N],num;int main() {    for(scanf("%d",&ty);ty;ty--) {        scanf("%d%d",&n,&m);maxn=(1<<n)-1;        memset(a,0,sizeof(a));        fo(i,1,n) scanf("%d",&v[i]);        if (n==1) {            printf("%d 1\n",v[1]);continue;        }        fo(i,1,m) scanf("%d%d",&x,&y),a[x][y]=a[y][x]=1;        memset(f,0,sizeof(f));memset(g,0,sizeof(g));        fo(i,1,n)            fo(j,1,n)                if (i!=j&&a[i][j]) {                    x=1<<(i-1);y=1<<(j-1);                    f[x+y][i][j]=v[i]*v[j]+v[i]+v[j];                    g[x+y][i][j]=1;                }        fo(i,0,maxn)            fo(j,1,n) {                x=1<<(j-1);                if (!(i&x)) {                    fo(k,1,n)                         fo(l,1,n)                            if (f[i][k][l]&&a[l][j]) {                                tot=f[i][k][l]+v[j]+                                v[l]*v[j]+a[k][j]*v[k]*v[l]*v[j];                                if (tot>=f[i+x][l][j]) {                                    if (tot==f[i+x][l][j])                                     g[i+x][l][j]+=g[i][k][l];                                    else g[i+x][l][j]=                                    g[i][k][l];f[i+x][l][j]=tot;                                }                            }                }            }num=ans=0;        fo(i,1,n)           fo(j,1,n)                if (f[maxn][i][j]>=ans) {                   if (f[maxn][i][j]==ans) num+=g[maxn][i][j];                   else num=g[maxn][i][j];ans=f[maxn][i][j];               }        printf("%d %lld\n",ans,num/2);    }}

T4 奶牛的图片

Description

给出一个1..n的排列,每一次可以交换相邻两个数,求用最小的步数使得序列合法。一个合法的序列是指和[1..n]循环同构的序列。

Input

第1行:一个单独的数N
第2到n+1行:第i+1行上的数表示第i个数.

Output

一个整数,表示是原序列变为一个合法的序列的最小花费。

Sample Input

5
3
5
4
2
1

Sample Output

2

Data Constraint

1<=n<=100,000

Solution

再遇神题
首先你需要知道,一个1..n的排列,通过互换相邻两个数,使其变成[1..n]的最小步数为其的逆序对个数
然后你需要知道,O(n log n)的逆序对的求法。归并排序呀~树状数组呀~都行。
再然后,你需要找出[a..n,1..a-1]和[a+1..n,1..a]的步数之间的关系,那么你就可以过了。
发现这两个序列中的[a+1..a-1]这一段是相同的,那我们需要求出,原序列中a的位置不变,其余位置变成形如这样的序列的最小步数。设[a..n,1..a-1]的步数为k1,a在原序列中的位置为p[a],那么很明显就是k1-(p[a]-1),然后我们再把它变回第二个序列,步数为(n-p[a]),所以总代价就是k1-(p[a]-1)+n-p[a],即k1+n+1-2*p[a]。很明显,这就是最小答案,枚举一下目标状态,递推出来即可。

Code

#include<cstdio>#include<algorithm>#define fo(i,a,b) for(int i=a;i<=b;i++)#define N 100005#define ll long longusing namespace std;int a[N],n,b[N],c[N];ll ans,k;void mergesort(int l,int r) {    if (l==r) return;    int mid=(l+r)/2;    mergesort(l,mid);mergesort(mid+1,r);    int i=l,j=mid+1,tot=l;    while (i<=mid&&j<=r)         if (a[i]<=a[j]) b[tot++]=a[i++];        else b[tot++]=a[j++],k=k+mid-i+1;    while (i<=mid) b[tot++]=a[i++];    while (j<=r) b[tot++]=a[j++];    fo(t,l,r) a[t]=b[t];}int main() {    scanf("%d",&n);    fo(i,1,n) scanf("%d",&a[i]),c[a[i]]=i;k=0;    mergesort(1,n);ans=k;    fo(i,1,n-1) k=k+n+1-2*c[i],ans=min(ans,k);    printf("%lld",ans);}

果然说把整场比赛的分数都押到只有一个点的第三题是作死的行为~最后都没有时间打第二,四题了,第三题忘记开longlong而爆了0,最后只得了100分,我不服!!!

0 0