【CCPC2022桂林站】【状压dp】B. Code With No Forces

6 篇文章 0 订阅

【题意】

由于题目很长绝对不是因为我懒 ,所以要看题意请点击该链接:
CCPC2022 桂林站 B. Code With No Forces

【基本思路】

【如何状压】
这道题大致的思路很显然,对于每一个测试程序,我们可以用3个二进制位0/1表示它的状态:目标verdict是否已达成,目标最大运行时间是否已经达成,目标最大运行空间是否已经达成。
注意,存在一些不合法的状态:当一个测试程序的目标verdict已经达成时,目标最大运行时间和目标最大运行空间必须已经达成,若时间和空间其中任何一个目标没有达成,那么该状态不合法。
那么,为了便于检查状态的合法性,我们用一个状态s的最低的m位0/1表示每个测试程序的目标verdict状态是否达成,用次低的m位0/1表示每个测试程序的目标时间状态是否达成,用最高的m位0/1表示每个测试程序的目标空间状态是否达成。对于一个状态s,当最低的m位0/1是次低的和最高的m位0/1的与运算结果的子集时,状态s是合法的。通过位运算,我们可以很容易实现上面这一判断。
【dp状态设计】
那么,出于直觉,我第一次写时设计出了如下状态:
d p [ i ] [ s ] dp[i][s] dp[i][s]表示考虑了前 i i i个测试数据选或不选,得到的状态为 s s s时选出的最少测试数据数量。
状态转移则是 d p [ i ] [ s ] + 1 − > d p [ i + 1 ] [ s ∣ ( 第 i 个 数 据 的 贡 献 ) ] , d p [ i ] [ s ] − > d p [ i + 1 ] [ s ] dp[i][s]+1->dp[i+1][s|(第i个数据的贡献)],dp[i][s]->dp[i+1][s] dp[i][s]+1>dp[i+1][s(i)],dp[i][s]>dp[i+1][s]
但是这样会MLE,因此可以去掉第一维度,仅保留第二维度,倒序枚举s就可以达到和上面转移同样的效果。这样实现代码后,我喜提一个WA。
仔细思考后,我发现这样的 d p dp dp方法不能考虑测试数据的重排序——无论是正序还是倒序枚举 i i i,都不可能考虑到所有的重排序可能性。
【dp状态再设计与状态转移】
进一步思考可以发现,如果我们把每一个状态s看作是一个点,把选择的下一个测试数据看作边,那么我们可以得到一个有向图。从dp转移可以发现,我们要进行的dp转移本质上是求解单源最短路径,答案就是从起始状态到目标状态的一条最短路径。如果不考虑自环的话,这个有向图甚至是一个DAG(有向无环图)。
但是这样的模型同样存在一个潜在的问题,我们可能多次选择同一个测试数据。但是多次选择同一个测试数据是不优的,因此我们无需担心我们构造出的最优解中出现这种情况。至此,由于边权均为1,我们用bfs就可以实现dp的转移。点数 O ( 8 m ) O(8^m) O(8m),每个点有 O ( n ) O(n) O(n)条出边,因此bfs的复杂度就是 O ( n ∗ 8 m ) O(n*8^m) O(n8m)
这样的思路看似很新奇,但也在情理之中:在求最优解的问题中,我们可以在不影响最优解的前提下,适当扩大合法解的集合,便于设计出更加有效的算法。在本题中,我第一次设计的dp转移能保证每个测试数据只被选择一次,但是很难实现重排序这一点。但是我们如果不再限制每个测试数据可以被选择的次数,就可以在不影响最优解的前提下得到一个图论模型。
【细节问题】
1.状态的合法性:在“如何状压”中已进行过详细阐释,这里不再赘述。
2.转移的合法性:对于一个状态s,若存在一个测试程序的verdict尚未达成,则这一步选择的测试数据对应的verdict必须要么是OK,要么是目标verdict。当且仅当一个测试数据的verdict已经达成时,我们才能选择大于该测试程序目标时间或空间的测试数据。以上两个条件都可以在预处理过后用位运算快速判断,具体实现可以参考我的程序中对于 v i s vis vis数组的处理。
3.Verdict为OK的测试程序的初始化:题目中明确说,即使是对于最终结果“OK,0/0”的程序,也至少需要通过一个测试点,因此不能一开始就把这样的程序的对应状态全部置1,防止特殊情况下起始状态和目标状态重叠导致答案输出0。

【代码实现】

#include<bits/stdc++.h>
#define re register
#define F(i,a,b) for(int re i=a;i<=b;i++)
#define D(i,a,b) for(int re i=a;i>=b;i--)
#define ll long long
#define mp make_pair
using namespace std;
const int N=405;
int n,m,s[N][11],a[N][11],b[N][11],ma[N],mb[N],aim[N],sta[N];
int vis[N],U,UU;
inline int red(){
	char ch=getchar();
	int data=0,w=0;
	while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
	if(ch=='-')ch=getchar(),w=1;
	while('0'<=ch&&ch<='9')data=(data<<1)+(data<<3)+ch-'0',ch=getchar();
	return w?-data:data;
}
char alpha(){
	char ch=getchar();
	while(ch<'A'||ch>'Z')ch=getchar();
	return ch;
}
int dp[1<<20|1]; 
struct node{
	int s,ans;
}pre[1<<20|1];
inline int cmin(int&x,int y){
	if(x>y){
		x=y;
		return 1;
	}
	return 0;
}
void print(int s){
	if(pre[s].s>0)print(pre[s].s);
	cout<<pre[s].ans<<" ";
}
inline int check(int s){
	return ((s>>m)&(s>>(2*m))&UU&s)!=(s&UU);
}
int main()
{
	n=red();m=red();
	F(i,1,n){
		F(j,1,m){
			char ch=alpha();
			if(ch!='O'&&!aim[j])
				aim[j]=ch;
			s[i][j]=ch;
			a[i][j]=red();
			b[i][j]=red();
		}
	}
	F(j,1,m){
		F(i,1,n){
			ma[j]=max(ma[j],a[i][j]);
			mb[j]=max(mb[j],b[i][j]);
			if(s[i][j]==aim[j])break;
		}
	}
	F(j,1,m){
		F(i,1,n){
			if(a[i][j]>ma[j]||b[i][j]>mb[j]||(s[i][j]!='O'&&s[i][j]!=aim[j]))
				vis[i]|=(1<<(j-1))|(1<<(j-1+m))|(1<<(j-1+2*m));
			if(s[i][j]==aim[j])sta[i]|=1<<(j-1);
			if(a[i][j]==ma[j])sta[i]|=1<<(j-1+m);
			if(b[i][j]==mb[j])sta[i]|=1<<(j-1+2*m);
		}
	}
	U=(1<<(3*m))-1,UU=(1<<m)-1;
	memset(dp,127/3,sizeof dp);
	dp[0]=0;
	queue<int>q;
	q.push(0);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int i=1;i<=n;i++){
			if(check(u|sta[i]))continue;
			if((vis[i]|u)!=u)continue;
			if(dp[u|sta[i]]>n){
				dp[u|sta[i]]=dp[u]+1;
				pre[u|sta[i]]=(node){u,i};
				q.push(u|sta[i]);
			}
		}
	}
	int S=U;
	F(j,1,m)if(!aim[j])S^=1<<(j-1);
	cout<<dp[S]<<"\n";
	print(S);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值