ZOJ 3777 Problem Arrangement 状压dp

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=5264

题意:

给定一个n*n的矩阵,在矩阵中选n个数字(每行每列只能选一个数字),问选出的数字和>=m的方法数

思路:

1、矩阵较小可以状压

2、每次转移以 数字和为记录,因为m<=500,对于数字和>m,可以视为m


相当于一个全排列中找方法数

即把n个数填入n个空格内。

则对于第i个数,我们不需要关心前面i-1个数所在的各个位置,只需要知道他们占了哪些位置

也就是 对于开始时 状态为 n个0,加入一个数可以转移到n位 二进制个数为1的状态。

一个优化是滚动,另一个是去掉前一个状态方法数为0的状态
dp[i][j][k] 表示前i件物品,在摆成二进制j状态下 和为k 的方法数

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
#include<math.h>
#include<vector>
#include<queue>
#include<set>
using namespace std;
#define N 12
#define ll int
ll n, m;
ll dp[2][5000][501];
ll mp[N][N];
ll Jie[20];
vector<ll>G[13];
ll Siz[13];
ll GCD(ll x,ll y){
	if(x>y)swap(x,y);
	while(x){
		y %= x;
		if(x>y)swap(x,y);
	}
	return y;
}
ll siz(ll x){ ll ans = 0; while(x){ans += x&1;x>>=1;} return ans; }
bool use[2][5000];
void init(ll x){memset(dp[x], 0, sizeof dp[x]);memset(use[x], 0, sizeof use[x]);}
int main(){
	int T;scanf("%d",&T);
	ll i, j;
	Jie[0] = 1;
	for(i = 1; i <=12; i++)Jie[i] = Jie[i-1]*i;
	for(i = 0; i <=12; i++)G[i].clear();
	for(i = 0; i <(1<<12); i++)	G[siz(i)].push_back(i);
	for(i = 0; i <= 12; i++)Siz[i] = G[i].size();
	while(T--){
		scanf("%d %d", &n, &m);
		for(i = 0; i < n; i++)	for(j = 0; j < n; j++)	scanf("%d",&mp[i][j]);
		init(0);
		ll cur = 0, top = 1, D = (1<<n);
		for(i = 1, j = 0; i < D; i<<=1, j++)
		{
			dp[0][i][min(m,mp[0][j])] = 1;
			use[cur][i] = 1;
		}
		for(i = 1; i < n; i++)
		{
			cur ^= 1;
			init(cur);
			for(j = 0; j < Siz[top]; j++)
			{
				ll now = G[top][j];
				if(now>=D)break;
				if(!use[cur^1][now])continue;
				for(ll z = 0; z < n; z++)if((now & (1<<z)) ==0)
				{
					ll t = now | (1<<z);
					bool yes = false;
					for(ll d = 0; d <= m; d++)
						if(dp[cur^1][now][d])
						{
							dp[cur][t][min(d + mp[i][z], m)] += dp[cur^1][now][d];
							yes = true;
						}
						use[cur][t] |= yes;
				}
			}
			top++;
		}
		if(dp[cur][(1<<n)-1][m]==0)puts("No solution");
		else
		{
			ll gcd = GCD(dp[cur][(1<<n)-1][m], Jie[n]);
			printf("%d/%d\n",Jie[n]/gcd, dp[cur][(1<<n)-1][m]/gcd);
		}
	}
	return 0;
}
/*
99
3 10
2 4 1
3 2 2
4 5 3
2 6
1 3
2 4
12 500
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 
5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 5000 



*/


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值