可耻地看了题解。。。本来想直接网络流的,然而不知道怎么得到最小的解,看网上有退流的做法可以求字典序最小的最小割集,不过那个退流要每次删掉边在跑增广路,至少是n^2的复杂度,这里1e5的点肯定是不行的。
于是想起cf可以看别人代码,于是肮脏地抄起了代码。发现了一种十分神奇的写法,a-f记为0-5,num[c]为字母c剩余的个数,st[i]为第i个位置可以放哪些字母的状态压缩,sum[i][s]记录从i到n有多少st[i]是s的子集。于是对于每个位置,枚举这个位置填的字母为c=0-5,num[c]--,如果对于所有状态s,其中所有字母的剩余个数总共为ssum,ssum全都>=sum[i+1][s],这就说明,当前位置放置c这个字母是合理的,因为如果ssum<sum[i+1][s]说明这个状态s的字母的总数如果小于st[i]是s的子集的i的数量,就因为st[i]是s的子集,就说明i这个位置必须填状态s的某一个字母,而总数又不够,说明是不行的。
复杂度为n*6*(1<<6)*6大约为2*1e8,而且由于st[i]的限制,总量会比这个更少,而且cf的评测姬很好,2s的时限是完全够的。结果显示我的时间是155ms。
#include<cstdio>
#include<cstring>
#define maxl 100010
const int cnt=(1<<6)-1;
int n,m,ans;
int st[maxl],son[cnt+10],num[6];
int sum[maxl][cnt+10];
char s[maxl],ch[7],anss[maxl];
inline void prework()
{
scanf("%s",s+1);
n=strlen(s+1);
for(int i=1;i<=n;i++)
num[s[i]-'a']++;
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
int pos;
scanf("%d",&pos);
scanf("%s",ch+1);
int l=strlen(ch+1);
for(int j=1;j<=l;j++)
{
int c=ch[j]-'a';
st[pos]|=(1<<c);
}
}
for(int i=1;i<=n;i++)
if(st[i]==0)
st[i]=cnt;
for(int i=n;i>=1;i--)
for(int j=1;j<=cnt;j++)
{
if((st[i]&j)==st[i])
son[j]++;
sum[i][j]=son[j];
}
}
inline void mainwork()
{
int ssum=0;bool flag;
for(int i=1;i<=n;i++)
{
ans=0;
for(int c=0;c<6;c++)
{
if(num[c]==0 || (st[i]&(1<<c))==0)
continue;
num[c]--;
flag=true;
for(int s=1;s<=cnt && flag;s++)
{
ssum=0;
for(int j=0;j<6;j++)
if(s&(1<<j))
ssum+=num[j];
if(ssum<sum[i+1][s])
flag=false;
}
if(flag)
{
ans=1;anss[i]=c+'a';
break;
}
num[c]++;
}
if(!ans)
return;
}
}
inline void print()
{
if(ans)
for(int i=1;i<=n;i++)
printf("%c",anss[i]);
else
printf("Impossible");
}
int main()
{
prework();
mainwork();
print();
return 0;
}
dinic版占坑。。。