JZWC【Day3】题解&总结

本文解析了四道ACM竞赛题目,包括最佳挑水、点固定、棋盘反转及二进制数查找等问题,通过状态压缩DP等算法高效解决。

神奇的Day3,看来是前一天的题太难了,这次放了一套水题,无压力AK。

T1 最佳挑水

Description

有n个东西,每一次你可以选两个东西带走,代价为cost[i][j],求把n个东西全部带走的最小代价。保证n为偶数。

Input

  输入文件中的的第一行为一个整数n。
  接下来的n行,每行有n个数,表示了cost矩阵。其中:cost矩阵中每一个数都是小于等于32768正整数,且cost[i][i]是没有用的。
  注意:cost[i][j]=cost[j][i]。

Output

  输出文件中仅一行为一个数,即最佳方案的最少代价。

Sample Input

4
0 100 5 100
100 0 100 11
5 100 0 100
100 11 100 0

Sample Output

16

Data Constraint

4<=n<=18

Solution

没有什么好害怕的了,一看数据范围就知道是状态压缩dp啦,随便乱搞就行了。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 20
#define M 300000
using namespace std;
int cost[N][N],n,m,f[M];
int main() {
    scanf("%d",&n);m=(1<<n)-1;
    fo(i,1,n)
        fo(j,1,n) scanf("%d",&cost[i][j]);
    memset(f,127,sizeof(f));f[0]=0;int inf=f[m];
    fo(i,0,m) 
        if (f[i]!=inf) 
            fo(j,1,n-1)
                fo(k,j+1,n) {
                    int x=1<<(j-1),y=1<<(k-1);
                    if (!(i&x)&&!(i&y))  
                        f[i+x+y]=min(f[i+x+y],f[i]+cost[j][k]);
                }
    printf("%d",f[m]);
}

T2 Fix

Description

给你一些点,其中一些点是固定的,然后还有一些没有固定的,然后问你固定所有点所用的线段的最小长度是多少。所谓固定,就是形如三角形的情况,就是两个固定的点向一个未固定的点连两条边,就能把未固定的点固定。

Input

输入文件有多组数据,每组数据的第一行是一个正整数n,表示有n个点。接下来n行,每行三个正整数x,y,c,表示每个点的坐标和固定状态,c=1为固定,0为未固定。
输入以0结尾

Output

对于每组数据,输出一个实数,表示把所有点都固定的最小长度,如果不能把所有点都固定,输出”No Solution”(不含引号)。答案保留六位小数。

Sample Input

4
0 0 1
1 0 1
0 1 0
1 1 0
3
0 0 1
1 1 0
2 2 0
0

Sample Output

4.414214
No Solution

Data Constraint

1 <= n <= 18,0 <= x, y < 100

Solution

同样用状压,注意优化。

Code

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define db double
#define N 20
#define M 300000
using namespace std;
db f[M],inf;
struct note{
    db x;int z;
}g[N][N];
int a[N],b[N],bz,n,m,tot,sum;
int sqr(int x) {return x*x;}
db len(int x,int y) {
    return sqrt(sqr(a[x]-a[y])+sqr(b[x]-b[y]));
}
bool cmp(note x,note y) {
    return x.x<y.x;
}
int main() {
    while (1) {
        scanf("%d",&n);if (!n) break;m=(1<<n)-1;tot=sum=0;
        fo(i,1,n) {
            scanf("%d%d%d",&a[i],&b[i],&bz);
            if (bz) sum+=1<<(i-1);
        }
        fo(i,1,n) {
            fo(j,1,n) g[i][j].x=len(i,j),g[i][j].z=j;
            sort(g[i]+1,g[i]+n+1,cmp);
        }
        memset(f,127,sizeof(f));inf=f[m];f[sum]=0;
        fo(i,0,m)
            fo(j,1,n) {
                int x=1<<(j-1);
                if (i&x)
                    fo(k,1,n) {
                        int y=1<<(g[j][k].z-1);
                        if (i&y&&g[j][k].z!=j) {
                            fo(l,1,n) {
                                int z=1<<(g[j][l].z-1);
                                if (i&z&&g[j][l].z!=
                                g[j][k].z&&g[j][l].z!=j) {
                                f[i]=min(f[i],f[i- x]+
                                g[j][k].x+g[j][l].x);break;}
                            }break;
                        }
                    }
            }
        if (f[m]!=inf) printf("%.6lf\n",f[m]);else printf("No Solution\n");
    }   
}

T3 玩诈欺的小杉

Description

给定一个n*m的棋盘,每一个格子在开始的状态时都是0,每一次可以选择一个格子把它和在它上下两格,左右一格范围内的格子的状态全部取反。求从原始状态到目标状态的方案数。

Input

  每组测试数据的第一行有3个正整数,分别是N和M和T
  接下来T个目标棋盘,每个目标棋盘N行,每行M个整数之前没有空格且非0即1,表示目标棋盘,两个目标棋盘之间有一个空行。

Output

  对每组数据输出T行,每行一个整数,表示能使初始棋盘达到目标棋盘的解法总数。

Sample Input

4 4 2
0010
0010
0111
0010

0010
0110
0111
0010

Sample Output

1
1

Data Constraint

对于30%的数据N,M<=15
对于100%的数据N,M<=20,T<=5

Solution

这套题中最难的题,也不过如此。
发现每一次操作都只会影响左右两列,那么当我们确定第一列的状态,我们是可以确定第二列的状态的。以此类推,知道第一列的状态就可以推出整个棋盘的状态,最后判断是否合法即可。

Code

#include<cstdio>
#include<cstring>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 21
using namespace std;
char st[N][N];
int n,m,t,i,j,k,l,a[N],ans,maxn;
int main() {
    for(scanf("%d%d%d",&n,&m,&t);t;t--) {
        fo(i,1,n) scanf("%s",st[i]+1);
        memset(a,0,sizeof(a));ans=0;maxn=(1<<n)-1;
        fo(i,1,m)
            fo(j,1,n) a[i]+=(st[j][i]-'0')*(1<<(j-1));
        fo(i,0,maxn) {
            int x=0,y=i;
            fo(j,1,m) {
                int t=y;
                y=y^a[j]^(y<<1)^(y<<2)^(y>>1)^(y>>2)^x;
                y=y&maxn;x=t;
            }
            if (y==0) ans++;
        }
        printf("%d\n",ans);
    }
}

T4 奶牛编号

Description

求出第N大的二进制数中有K个1的数

Input

只有1行:空格隔开的两个整数,N和K。

Output

如题,第N大的数,转成二进制输出。

Sample Input

7 3

Sample Output

10110

Data Constraint

1<=K<=10,1<=N<=10^7

Solution

一道好题,有许多种神奇的方法,讲讲我的方法。
发现对于在从右往左数的第n个位置是1,总共有k个1的数有(n1k1)种,那么我们可以预处理处一个组合数的前缀和,每一次用二分找出第一个总方案数大于等于N的地方放1,然后在处理类似的一个子问题,便可构造出答案。注意当K=1时要特判,不然答案的位数会过大。设len为答案的长度,则时间复杂度为O(log len*k)。

Code

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define N 10005
#define ll long long
using namespace std;
int k,a[N],n;
ll c[N][11],sum[N][11];
void dfs(int x,int y,int z) {
    if (x==0||y==0) return;
    int l=1,r=x+1,mid;
    while (l<r) {
        mid=(l+r)/2;
        if (sum[mid-1][y-1]>=z) r=mid;else l=mid+1;
    }a[l]=1;dfs(l-1,y-1,z-sum[l-2][y-1]);
}
int main() {
    scanf("%d%d",&n,&k);
    if (k==1) {
        printf("1");fo(i,2,n) printf("0");return 0;
    }
    fo(i,1,N-1) c[i][0]=1,sum[i][0]=i+1;
    c[1][1]=c[0][0]=sum[1][1]=sum[0][0]=1;
    fo(i,2,N-1)
        fo(j,1,10) {
           c[i][j]=min(c[i-1][j-1]+c[i-1][j],1ll*n+1);
           sum[i][j]=min(c[i][j]+sum[i-1][j],1ll*n+1);
        }
    int l=1,r=N,mid;
    while (l<r) {
        mid=(l+r)/2;
        if (sum[mid-1][k-1]>=n) r=mid;else l=mid+1;
    }
    a[l]=1;dfs(l-1,k-1,n-sum[l-2][k-1]);
    fd(i,l,1) printf("%d",a[i]);
}

看来老师是体谅我们上次两套题出得太难,这次特意放了一套水题,让我们找回信心,终于可以好好滴耍寒假作业了~~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值