Codeforces #309 Div 1 简要题解

A. Kyoya and Colored Balls

题目链接

http://codeforces.com/contest/553/problem/A

题目大意

k 种颜色的球,每种ci个,要求第 i 种球的最后一个球要在第i+1种球的最后一个球之前放置。问有多少种合法的放置球的方案。

思路

我们可以初始先在这个放置序列里填入每种颜色最后一个球,然后从1号球到k号球,填入每种球, ci1 个第i个球都必须填入到第i个球的最后一个球之前

初始时只有一种方案:
1 2 3 4… k
然后我们放入 c11 个1号球,方案数为 Cc11c11
1111 2 3 4 … k
然后我们放入 c21 个1号球,方案数为 Cc21c1+c21
……

也就是说,填入第 i 号球时,方案数为

CCi1(Ci1)i()

我们在统计答案的过程中维护 Ci 的前缀和来对这个做法进行优化

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 1100
#define MOD 1000000007

using namespace std;

typedef long long int LL;

LL C[MAXN][MAXN];

LL fastPow(LL base,LL pow)
{
    LL ans=1;
    while(pow)
    {
        if(pow&1) ans=ans*base%MOD;
        base=base*base%MOD;
        pow>>=1;
    }
    return ans;
}

int K;
LL sum=0;

int main()
{
    C[0][0]=1;
    for(int i=1;i<MAXN;i++)
        for(int j=0;j<=i;j++)
        {
            if(j==0||j==i) C[i][j]=1;
            else C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
        }
    LL ans=1;
    scanf("%d",&K);
    for(int i=1;i<=K;i++)
    {
        int x;
        scanf("%d",&x);
        sum+=x;
        ans=ans*C[sum-1][x-1]%MOD;
    }
    printf("%I64d\n",ans);
    return 0;
}

B. Kyoya and Permutation

题目链接

http://codeforces.com/contest/553/problem/B

题目大意

给你一个置换的加法,如 (142)(36)(5),置换得到的序列是[4, 1, 6, 2, 5, 3]
定义一个置换加法式子的cyclic representation是将每个置换环把最大的数字放在环的最前面,去掉所有的括号后得到的序列,如 (142)(36)(5)<=>(421)(63)(5)–>421635

定义Kyoya’s permutation为cyclic representation和置换得到的序列相同的序列,求长度为n的字典序第 k 小的Kyoya’s permutation

思路

首先要知道一个结论,所有的Kyoya’s permutation,都是由排列12345…,从某些相邻的元素交换而得到的,而且不存在ai aj 交换,而 aj 又和 ak 交换的情况。
证明窝不会。。。但是可以自己找规律发现这个问题

那么我们可以得到一个dp方程: f[i]= 长度为i的Kyoya’s permutation个数。则 f[i]=f[i1]+f[i2] (第i个元素要么选择不动,方案数f[i-1],要么选择和i-1号元素进行交换,方案数f[i-2])
初始f[1]=1,f[2]=2,这是什么?fibonacci数!
然后剩下的问题比较像数位DP了,告诉你每个长度的Kyoya’s permutation个数,求出第k个Kyoya’s permutation。

显然对于i和i+1号元素,其他部分相同的序列A和B,若A的这两个元素没交换,B的这两个元素交换了,那么A在字典序里就小于B。这个性质在下面会得到充分利用

我们可以这样:从左到右枚举Kyoya’s permutation的下标i,初始时序列为12345…,假设第i个元素不选择与第i+1个元素交换,那么得到的序列的i~n部分在所有的数字i,i+1…n的排列里的字典序编号是在 [1,fib[1,ni]] 里的,假设第i个元素选择与第i+1个元素交换,那么得到的序列的i~n部分在所有的数字i,i+1…n的排列里的字典序编号是在 [fib[ni]+1,fib[ni+1]] 里的。这个请自己仔细研究搞明白。

如果 K>fib[ni] (这个K是答案序列的i~n部分在所有的数字i,i+1…n的排列里的字典序编号),很显然就说明之后i+1~n号元素的排列就算是字典序编号最大的Kyoya’s permutation,整个序列的字典序编号也会小于K,那么就需要交换i和i+1,K-= fib[ni] ,然后再去求答案序列里i+2~n那部分。

其实这个过程也很像平衡树查询第k小数,思想上差不多,大家自己研究透彻就能发现其中的异曲同工之妙了

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

using namespace std;

typedef long long int LL;

int n;
LL K;
int ans[100];
LL fib[100];

int main()
{
    scanf("%d%I64d",&n,&K);
    fib[0]=fib[1]=1,fib[2]=2;
    for(int i=3;i<=n;i++)
        fib[i]=fib[i-1]+fib[i-2];
    for(int i=1;i<=n;i++) ans[i]=i;
    for(int i=1;i<=n;i++)
        if(K>fib[n-i])
        {
            K-=fib[n-i];
            swap(ans[i],ans[i+1]);
            i++;
        }
    for(int i=1;i<=n;i++)
        printf("%d ",ans[i]);
    printf("\n");
    return 0;
}

C. Love Triangles

题目链接

http://codeforces.com/contest/553/problem/C

题目大意

给出一个n个点的无向完全图,图中每条无向边要么是红色要么是蓝色,并给出其中m条事先已经染好色的边的颜色。定义这个图的一种边的染色是完美的, i,j,k ,三点之间连接的三条边,要么是红,蓝,蓝;要么是红,红,红。问有多少种完美的染色方案。

思路

假设已经知道了边i-j的颜色,那么对于点1,i,j而言:
若i-j是红色,那么1-i和1-j要么都是红色,要么都是蓝色
若i-j是红色,那么1-i和1-j中一个是红色,另一个是蓝色

因此只要知道i-j,就可以知道1-i与1-j的关系了。若i-j为红色,则1-i和1-j同色;若i-j为蓝色,则1-i和1-j异色。而1连出的边共有n-1条,我们就能维护一个n-1个点的种类并查集。对于每个已经知道颜色的边i-j,根据边的颜色来合并i和j所在的联通块,若i和j已经在同一联通块里,而现在又出现了种类上的冲突,就可以判定为无解。

对于最后的并查集里每个联通块,都有2种完全截然相反的染色方法,假设联通块有t个,那么染色方案数为 2t

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXV 110000
#define MAXE 110000
#define MOD 1000000007

using namespace std;

typedef long long int LL;

int f[MAXV],col[MAXV];
int n,m;

int findSet(int x)
{
    if(f[x]==x) return x;
    int tmp=findSet(f[x]);
    col[x]^=col[f[x]]; //!!!!一开始col[x]是x和其父亲的相对关系(1表示相反类型,0表示相同类型),当父亲的颜色变化后,对col[x]亦或上父亲的颜色即可(若其与父亲关系相反,那么亦或后颜色会和父亲相反,反之亦或后和父亲颜色相同)
    f[x]=tmp;
    return f[x];
}

LL ans=1;
int tot=0; //并查集联通块的个数

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) f[i]=i;
    tot=n-1;
    for(int i=1;i<=m;i++)
    {
        int u,v,color;
        scanf("%d%d%d",&u,&v,&color); //边u-v是红色(1),则u和v相同,否则相反
        color^=1;
        int rootu=findSet(u),rootv=findSet(v);
        if(!ans) continue;
        if(rootu!=rootv)
        {
            col[rootu]=col[u]^col[v]^color;
            f[rootu]=rootv;
            tot--;
        }
        else
        {
            if((col[u]^col[v]^color)!=0)
                ans=0;
        }
    }
    for(int i=1;i<=tot;i++) ans=ans*2%MOD;
    printf("%I64d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值