第一天,题目难度适中易改,出现了以前模拟赛做过的,但却没有做出来。
T1.水叮当的舞步
题目数据范围较小,考虑搜索。在每一步枚举当前选择的颜色。
此时时间复杂度为 \(O(ans^6)\),\(ans\) 最坏情况下为 \(N\times N\),若将搜索树整颗遍历完显然时间 \((N^{12})\) 是不能接受的。
因此:
-
考虑限制搜索深度以提高搜索效率 (IDA*) 。
-
考虑某个状态下,达到目标状态还需要的步数最小可能是剩下的颜色种类,作为一个剪枝。
IDA* + 剪枝 即可通过本题。
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;
template<class T>void read(T &x){
x=0; bool f=0; char c=getchar();
while(c<'0'||'9'<c){f|=(c=='-'); c=getchar();}
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
x=f?-x:x;
}
const int N=10;
int n,lim;
int a[N][N],cnt,t[N];
struct node{
int x,y;
}p[N*N];
bool in[N][N];
int dx[5]={0,1,0,-1};
int dy[5]={1,0,-1,0};
void init() {
memset(a,0,sizeof(a));
memset(t,0,sizeof(t));
memset(p,0,sizeof(p));
memset(in,0,sizeof(in));
lim=cnt=0;
}
void ins(int c) {
for(int i=1;i<=cnt;i++)
for(int j=0;j<=3;j++) {
int nx=p[i].x+dx[j], ny=p[i].y+dy[j];
if(nx<1||ny<1||nx>n||ny>n) continue;
if(a[nx][ny]==c) if(!in[nx][ny]) {
--t[a[nx][ny]];
p[++cnt]=(node){nx,ny};
in[nx][ny]=1;
}
}
}
void del(int rem) {
while(cnt>rem){
++t[a[p[cnt].x][p[cnt].y]];
in[p[cnt].x][p[cnt].y]=0;
p[cnt--]=(node){0,0};
}
}
bool dfs(int dep){
// if(dep>lim) return 0;
int tmp=0;
for(int i=0;i<=5;i++) if(t[i]) ++tmp;
if(dep+tmp>lim) return 0;
if(cnt==n*n) return 1;
for(int i=0;i<=5;i++) {
tmp=cnt; ins(i);
if(tmp!=cnt) {
bool suc=dfs(dep+1);
del(tmp);
if(suc) return 1;
}
}
return 0;
}
void solve() {
init();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++) {
read(a[i][j]);
++t[a[i][j]];
}
in[1][1]=1;
p[++cnt]=(node){1,1};
--t[a[1][1]];
ins(a[1][1]);
while(lim<=n*n) {
if(dfs(0)) {
printf("%d\n",lim);
return ;
}
++lim;
}
}
int main() {
// freopen("a.in","r",stdin);
// freopen("a.out","w",stdout);
read(n);
while(n) {
solve();
read(n);
}
return 0;
}
T2.Vani和Cl2捉迷藏
题意是求一张有向图的最大反链。反链即一个点集,其中任意两点间不能到达。
在图上跑传递闭包,图转化为一张二分图,匈牙利算法求最大独立集即可通过本题。(原题 \(CTSC2008 river\))
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
template<class T>void read(T &x){
x=0; char c=getchar();
while(c<'0'||'9'<c)c=getchar();
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}
const int N=205;
int n,m,ans;
int match[N];
bool f[N][N],vis[N];
bool dfs(int x){
for(int y=1;y<=n;y++) if(f[x][y]) if(!vis[y]) {
vis[y]=1;
if(!match[y]||dfs(match[y])){
match[y]=x;
return 1;
}
}
return 0;
}
int main(){
read(n); read(m);
int x,y;
for(int i=1;i<=m;i++){
read(x); read(y);
f[x][y]=1;
}
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
f[i][j]|=f[i][k]&&f[k][j];
for(int i=1;i<=n;i++){
memset(vis,0,sizeof(vis));
if(dfs(i)) ++ans;
}
printf("%d\n",n-ans);
return 0;
}
T3.粉刷匠
最朴素的想法是 \(f_{i,r1,r2,...,rk}\) 表示粉刷到第 \(i\) 根石柱,\(k\) 种颜色分别剩下 \(r_i\) 桶的情况。时间和空间上都不能接受。
上面的做法可以利用 \(c\) 的取值范围来优化,把 k 种颜色剩下的桶数按剩下的数量分组,可以通过本题,代码待写。
换种思路,不考虑逐个计算每一根柱子,考虑把上好色的柱子放进处理好的柱子里。
使用另一种状态的定义方式 \(f_{i,j}\) 表示当前处理完了前i种颜色,用j对柱子颜色相同。
每次把第 \(i\) 种颜色插进前面的柱子里,只有两种情况:插到 \(2\) 根同色的柱子间,插到 \(2\) 根异色的柱子间。
所以第 \(i\) 种颜色插进去会减少 \(j\) 对同色柱子的颜色,同时产生一些同色柱子。
我们决定把 \(c\) 根 \(i\) 色柱子拆成 \(x\) 块,把其中 \(y\) 块放进同色柱子间,其他放进异色柱子间。
不管怎么分,\(c\) 根柱子分成 \(x\) 块都必然有 \(c-x\) 对同色,\(j\) 增加了 \(c-x\)。方案数 \(C^{x-1}_{c-1}\)
因为 \(y\) 块放进了已经存在的 \(j\) 对同色柱子间,\(j\) 减少 \(y\)。方案数 \(C^{y-1}_{j-1}\)
剩下的 \(x-y\) 块柱子就放进 \(n-j+1\) 对异色柱子间。方案数 \(C^{n-j+1}_{x-y}\)
代码:
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
template<class T>void read(T &x){
x=0; bool f=0; char c=getchar();
while(c<'0'||'9'<c){f|=(c=='-'); c=getchar();}
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
x=f?-x:x;
}
typedef long long ll;
const int N=20;
const int M=1000000007;
int k,n,a[N];
ll f[N][N*N],c[N*N][N*N];
void add(ll &x,ll y){x+=y; if(x>=M)x%=M;}
void pre(){
c[0][0]=1;
for(int i=1;i<=100;i++){
c[i][0]=c[i][i]=1;
for(int j=1;j<=i;j++) c[i][j]=c[i-1][j]+c[i-1][j-1];
}
}
void solve(){
f[0][0]=1;
for(int i=1;i<=k;i++){
for(int j=0;j<=n;j++)
for(int x=0;x<=a[i];x++){
int in=min(x,j);
for(int y=0;y<=in;y++)
add(f[i][j+a[i]-x-y],f[i-1][j]*c[j][y]%M*c[a[i]-1][x-1]%M*c[n-j+1][x-y]);
}
n+=a[i];
}
printf("%lld\n",f[k][0]);
}
int main() {
// freopen("c.in","r",stdin);
pre();
int T; read(T);
while(T--) {
read(k); n=0;
memset(a,0,sizeof(a));
memset(f,0,sizeof(f));
for(int i=1;i<=k;i++) read(a[i]);
solve();
}
return 0;
}