神奇的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的数有(n−1k−1)种,那么我们可以预处理处一个组合数的前缀和,每一次用二分找出第一个总方案数大于等于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]);
}
看来老师是体谅我们上次两套题出得太难,这次特意放了一套水题,让我们找回信心,终于可以好好滴耍寒假作业了~~
本文解析了四道ACM竞赛题目,包括最佳挑水、点固定、棋盘反转及二进制数查找等问题,通过状态压缩DP等算法高效解决。

4935

被折叠的 条评论
为什么被折叠?



