JZWC【Day4】题解&总结

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

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 305
using 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 1025
using 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 long
using 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 long
using 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分,我不服!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值