【模拟赛】纪中提高A组 19.8.1 测试

Posted on 2019-08-16 11:34  opethrax  阅读(...)  评论(... 编辑 收藏

第一天,题目难度适中易改,出现了以前模拟赛做过的,但却没有做出来。

T1.水叮当的舞步

题目数据范围较小,考虑搜索。在每一步枚举当前选择的颜色。

此时时间复杂度为 \(O(ans^6)\)\(ans\) 最坏情况下为 \(N\times N\),若将搜索树整颗遍历完显然时间 \((N^{12})\) 是不能接受的。

因此:

  1. 考虑限制搜索深度以提高搜索效率 (IDA*) 。

  2. 考虑某个状态下,达到目标状态还需要的步数最小可能是剩下的颜色种类,作为一个剪枝。

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;
}