【题意】
由于题目很长绝对不是因为我懒 ,所以要看题意请点击该链接:
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(n∗8m)。
这样的思路看似很新奇,但也在情理之中:在求最优解的问题中,我们可以在不影响最优解的前提下,适当扩大合法解的集合,便于设计出更加有效的算法。在本题中,我第一次设计的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);
}