省队集训DAY4

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/clover_hxy/article/details/72834554

T1

这里写图片描述
这里写图片描述

题解

将行与列分开考虑,每两个#之间属于一个连通块。
对于每个连通块建立节点,如果只从连通块中选取一个点,那么不会产生相互攻击的棋子。选取第2个点的时候会产生1的贡献,选取第三个点的时候会产生2的贡献。。。。
行列都是如此,那么S->行所代表的连通块,列代表的连通块->T。对于每个连通块连size条边,每条边的容量为1,费用依次递增。
对于每个不是#的位置,一定属于两个连通块,在两个连通块之间连容量为1,费用为0的边,保证每个点只会被选择一次。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#define N 500003
#define inf 1000000000
using namespace std;
int tot,point[N],v[N],nxt[N],remain[N],c[N],dis[N],can[N],last[N];
int belong[53][53],belong1[53][53],mp[53][53],size[N];
int n,m,ans,cnt,S,T,TT,ck[N];
void add(int x,int y,int z,int k)
{
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remain[tot]=z; c[tot]=k;
    tot++; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remain[tot]=0; c[tot]=-k;
    //cout<<x<<" "<<y<<" "<<z<<" "<<k<<endl;
}
int addflow(int s,int t)
{
    int ans=inf; int now=t;
    while (now!=s) {
        ans=min(ans,remain[last[now]]);
        now=v[last[now]^1];
    }
    now=t;
    while (now!=s) {
        remain[last[now]]-=ans;
        remain[last[now]^1]+=ans;
        now=v[last[now]^1];
    }
    return ans;
}
int spfa(int s,int t)
{
    for (int i=1;i<=t;i++) dis[i]=inf,can[i]=0;
    dis[s]=0; queue<int> p; p.push(s); can[s]=1;
    while (!p.empty()){
        int now=p.front(); p.pop();
        can[now]=0;
        for (int i=point[now];i!=-1;i=nxt[i])
         if (remain[i]&&dis[v[i]]>dis[now]+c[i]){
            dis[v[i]]=dis[now]+c[i];
            last[v[i]]=i;
            if (!can[v[i]]) {
                can[v[i]]=1;
                p.push(v[i]);
             }
         }
    }
    int flow=addflow(s,t);
    ans+=dis[t];
}
int main()
{
    freopen("chess.in","r",stdin);
    freopen("chess.out","w",stdout);
    scanf("%d",&n); int sum=0;
    tot=-1;
    memset(point,-1,sizeof(point));
    for (int i=1;i<=n;i++) {
        char s[53]; scanf("%s",s+1);
        for (int j=1;j<=n;j++)
         if (s[j]=='#') mp[i][j]=1;
         else sum++;
    }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++){
         if (mp[i][j]) continue;
         if (j==1||mp[i][j-1]) ++cnt;
         belong[i][j]=cnt;
         size[cnt]++;
        }
    int k=cnt;
    for (int i=1;i<=n;i++)
       for (int j=1;j<=n;j++){
        if (mp[j][i]) continue;
        if (j==1||mp[j-1][i]) ++cnt;
        belong1[j][i]=cnt;
        size[cnt]++;
       }
    S=cnt+1; T=S+1; TT=T+1;
    for (int i=1;i<=k;i++) {
        add(S,i,1,0);
        for (int j=1;j<size[i];j++) add(S,i,1,j);
    }
    for (int i=k+1;i<=cnt;i++) {
        add(i,T,1,0);
        for (int j=1;j<size[i];j++) add(i,T,1,j);
    }
    for (int i=1;i<=n;i++)
     for (int j=1;j<=n;j++)
      if (!mp[i][j]) add(belong[i][j],belong1[i][j],1,0);
    for (int i=1;i<=sum;i++){
        add(T,TT,1,0);
        spfa(S,TT);
        ck[i]=ans;
    }
    scanf("%d",&m);
    for (int i=1;i<=m;i++){
        int x; scanf("%d",&x);
        printf("%d\n",ck[x]);
    }
}

T2

这里写图片描述

题解

考虑最暴力的做法,n!枚举所有排列,对于每种排列在没有多余空隙的情况下占的空隙数为 n1i=1max(ai,ai+1)
对于每种排列我们求出占的空隙数x,那么剩下的l-1-x个空隙可以任意插入到n+1个空隙中。
实际上就是插板发l-1-x个空隙,分到n+1个区间中,允许区间为空,答案为 C(lx+n1,n)
算法的瓶颈在于n!的排列。
如果我们能求出空隙数为x的排列数,那么就可以直接利用组合数计算了。
考虑从小到大依次插入
f[i][j][k] 表示插入到第i个数,j个能插入的位置,占的空隙数为k的方案数。注意能放数的位置不包括两边,所以初始值是 dp[1][0][0]=1
因为是从小到大插入的,所以如果插入到两个已经插入的数之间且不再从这个数两侧插入数,那么对于空隙的贡献就是插入的数*2,能插入的位置-1
如果插入一个位置,只保证插入位置的一侧不能再插入,那么对于空隙的贡献就是插入的数,能插入的位置不变。
如果插入一个位置,两侧都可以再插入数(再插入的数一定比当前数大),那么对于空隙的贡献就是0,能插入的位置+1.
两头的位置单独考虑一下。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 101
#define LL long long
using namespace std;
int n,l;
int dp[N][N][N*N],p;
int cnt,prime[N],c[N],tmp[N],pd[N],sum[N];
void init()
{
    for (int i=2;i<=n;i++){
        if (!pd[i]) prime[++cnt]=i;
        for (int j=1;j<=cnt;j++){
            if (prime[j]*i>n) break;
            pd[prime[j]*i]=1;
            if (i%prime[j]==0) break;
        }
    }
}
LL C(int K)
{
    LL ans=1;
    for (int i=1;i<=cnt;i++) tmp[i]=0;
    for (int i=K-n+1;i<=K;i++){
        int x=i;
        for (int j=1;j<=cnt;j++)
         while (tmp[j]<c[j]&&x%prime[j]==0)
          x/=prime[j],tmp[j]++;
        ans=(LL)ans*x%p;
    }
    return ans;
}
int main()
{
    freopen("tower.in","r",stdin);
    freopen("tower.out","w",stdout);
    scanf("%d%d%I64d",&n,&l,&p);
    init(); 
    for (int i=1;i<=n;i++) {
        int x=i;
        for(int j=1;j<=cnt;j++)
            while (x%prime[j]==0) c[j]++,x/=prime[j];   
    }
    for (int i=1;i<=n;i++) sum[i]=sum[i-1]+2*i;
    dp[1][0][0]=1;
    for (int i=1;i<n;i++) {
        for (int j=1;j<=min(i-1,n-i);j++)
         for (int k=0;k<=sum[i];k++){
          dp[i+1][j-1][k+(i+1)*2]=(dp[i+1][j-1+0][k+(i+1)*2]+(LL)j*dp[i][j][k]%p)%p;
          dp[i+1][j][k+(i+1)]=(dp[i+1][j][k+(i+1)]+(LL)2*j*dp[i][j][k]%p)%p;
          dp[i+1][j+1][k]=(dp[i+1][j+1][k]+(LL)j*dp[i][j][k]%p)%p;
        }
        for (int j=0;j<=min(i-1,n-i);j++)
         for (int k=0;k<=sum[i];k++){
            dp[i+1][j][k+(i+1)]=(dp[i+1][j][k+(i+1)]+(LL)2*dp[i][j][k]%p)%p;
            dp[i+1][j+1][k]=(dp[i+1][j+1][k]+(LL)2*dp[i][j][k]%p)%p;
         }
    }
    LL ans=0;
    for (int i=0;i<=sum[n];i++){
        LL t=C(l-i+n-1);
        ans=(ans+(LL)t*dp[n][0][i]%p)%p;
    }
    printf("%I64d\n",ans);
}

猜你喜欢

转载自blog.csdn.net/clover_hxy/article/details/72834554