Codeforces #309 div1

2015-06-26 21:43:37

传送门

总结:这场做出了两题,都是数学类的,速度还可,回到黄咯~

反思:第一题没看到数据范围,用了比较麻烦的逆元预处理。第二题纠结题意过久。第三题思维不够强,没想到....

 

A题:组合数

  题意:有 k 种颜色的球,颜色号分别为1~k,每种球 ci 个(1 <= k,c <= 1000),让排成一排,使得第 i 种颜色的最后一个球必须在第 i-1 种颜色的最后一个球的后面,问有多少种这样的排列。

  思路:从末尾往前面的考虑,最后一个位置必定放第 k 种颜色的球,那么该种颜色剩下的 ck-1 个球就随便放了,有 C(n-1,ck-1)种,这样剩下 n - ck 个位置,再考虑第 k-1 种颜色的球,以此类推即可。就是:C(n-1,ck-1) * C(n-ck-1,c(k-1) - 1) * ....

#include <cstdio>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

#define getmid(l,r) ((l) + ((r) - (l)) / 2)
#define MP(a,b) make_pair(a,b)
#define PB(a) push_back(a)

typedef long long ll;
typedef pair<int,int> pii;
const double eps = 1e-8;
const int INF = (1 << 30) - 1;
const ll mod = 1000000007LL;
const int MAXN = 1000000;

int k,c[1010],sum;
ll fac[MAXN + 10],afac[MAXN + 10];

ll Q_pow(ll x,ll y){
    x %= mod;
    ll res = 1;
    while(y){
        if(y & 1) res = res * x % mod;
        x = x * x % mod;
        y >>= 1;
    }
    return res;
}

void Pre(){
    fac[0] = afac[0] = 1;
    for(int i = 1; i <= MAXN; ++i) fac[i] = fac[i - 1] * (ll)i % mod;
    afac[MAXN] = Q_pow(fac[MAXN],mod - 2);
    for(int i = MAXN; i >= 1; --i) afac[i - 1] = afac[i] * i % mod;
}

ll C(int n,int m){
    if(m > n) return 0;
    return fac[n] * afac[n - m] % mod * afac[m] % mod;
}

int main(){
    Pre();
    sum = 0;
    scanf("%d",&k);
    for(int i = 1; i <= k; ++i){
        scanf("%d",&c[i]);
        sum += c[i];
    }
    ll ans = 1;
    for(int i = k; i >= 1; --i){
        ans = ans * C(sum - 1,c[i] - 1) % mod;
        sum -= c[i];
    }
    printf("%I64d\n",ans % mod);
    return 0;
}
View Code

 

B题:DP,逆康托

  题意:给出 n,对于 n 个数的一个排列,求出其所有置换群,并且每个置换群第一个数字为其群内最大的数,然后按照每个群的一个数升序对群进行排序,问字典序第k个 n 个数的排列满足在进行上述操作之后和原排列保持一致。比如 n = 5 , 2 1 3 4 5 就满足。题意描述有点问题(我是按置换群内降序写的)

  思路:发现规律:对于 1,2,3...n 这样的排列(首先这个排列是符合条件的),如果要交换也只能交换相邻的数字,而且交换之间不能重叠。那么就是逆康托的过程,先用 dp 预处理 1~n 个数的排列有多少种,dp[i][0]表示i个数的排列,且最后一个数不交换的种数;dp[i][1]表示i个数的排列,且最后一个数与i-1个数交换的种类数,于是就有 dp[i][0] = dp[i - 1][1] + dp[i - 1][0] ;  dp[i][1] = dp[i - 1][0] ,发现 dp[i][0] + dp[i][1] = Fib(i + 1),即第 i+1 项斐波那契数。

  由于 n 最大只有50,直接预处理之后用逆康托的思想来一遍循环。

#include <cstdio>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

#define getmid(l,r) ((l) + ((r) - (l)) / 2)
#define MP(a,b) make_pair(a,b)
#define PB(a) push_back(a)

typedef long long ll;
typedef pair<int,int> pii;
const double eps = 1e-8;
const int INF = (1 << 30) - 1;

ll fib[100];
ll N,K;
int ans[100];

void Fib(){
    fib[1] = 1;
    for(int i = 2; i <= 52; ++i)
        fib[i] = fib[i - 1] + fib[i - 2];
}

int main(){
    Fib();
    cin >> N >> K;
    for(ll i = 1; i <= N; ++i){
        if(K <= fib[N - i + 1]){
            ans[i] = i;
        }
        else{
            ans[i] = i + 1;
            ans[i + 1] = i;
            K -= fib[N - i + 1];
            i++;
        }
    }
    for(ll i = 1; i <= N; ++i)
        printf("%d ",ans[i]);
    puts("");
    return 0;
}
View Code

 

C题:DFS,图论

  题意:给出一幅 n 个点的无向图,已知有 m 条无向边,有黑边有白边,让你把它补成完全图,使得任意三个点的三条边要么全是白边,要么只有一条白边,问你有多少种补全图的方法。

  思路:首先发现两条边可以确定第三条边,那么只要点连通,这个连通块内的所有点之间的边的颜色都已经确定。

  不同联通块之间连接也只有两种方法,所以只要找出有多少个连通块,比如 k 个,答案就是 2^(k-1)。

  至于如何判断不可行,是找规律的.... 如果黑边权值为1,白边权值为0,那么如果存在权值和为奇数的环就不可行,这可以用DFS实现找奇环。

#include <cstdio>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

#define getmid(l,r) ((l) + ((r) - (l)) / 2)
#define MP(a,b) make_pair(a,b)
#define PB(a) push_back(a)

typedef long long ll;
typedef pair<int,int> pii;
const double eps = 1e-8;
const int INF = (1 << 30) - 1;
const int MAXN = 100010;
const ll mod = 1000000007;

int n,m,tot,ecnt;
int first[MAXN];
int vis[MAXN],dep[MAXN];
bool flag;

struct edge{
    int v,next,c;
}e[MAXN << 1];

inline void add_edge(int u,int v,int c){
    e[ecnt].next = first[u];
    e[ecnt].v = v;
    e[ecnt].c = c;
    first[u] = ecnt++;
}

void Dfs(int p,int pre,int s){
    if(flag == false) return;
    vis[p] = 1;
    dep[p] = s;
    for(int i = first[p]; ~i; i = e[i].next){
        int v = e[i].v;
        if(v == pre) continue;
        if(vis[v] && (dep[p] - dep[v] + !e[i].c) & 1){
            flag = false;
            return;
        }
        if(!vis[v]) Dfs(v,p,s + !e[i].c);
    }
}

int main(){
    memset(first,-1,sizeof(first));
    ecnt = 0;
    int a,b,c;
    scanf("%d%d",&n,&m);
    for(int i = 1; i <= m; ++i){
        scanf("%d%d%d",&a,&b,&c);
        add_edge(a,b,c);
        add_edge(b,a,c);
    }
    flag = true;
    tot = 0;
    for(int i = 1; i <= n; ++i) if(!vis[i]){
        tot++;
        Dfs(i,-1,0);
        if(flag == false) break;
    }
    if(flag == false){
        printf("0\n");
        return 0;
    }
    ll ans = 1;
    for(int i = 1; i < tot; ++i) ans = ans * 2LL % mod;
    printf("%I64d\n",ans);
    return 0;
}
View Code

 

D题:贪心 / 二分,优先队列,图论

  题意:给出一个 n 个点的无向图,m 条边。每个点是黑色或者白色,一开始有 k 个黑点。每个点有一个权值,为(相邻白色点数)/ (相邻点数),你可以把某些点染黑,使得所有白点的权值的最小值最大。问此时有哪些点为白点。

  思路:一开始打的二分答案,但是精度一直搞不定。换了贪心。

  首先把所有(白点及其权值)加进优先队列里面,让权值最小的排在顶部,然后每次取一个白点,因为他的权值最小,所以如果可以更新答案,就记录此时的白点情况,然后将其染色,并且更新与其相邻的所有白点的权值,将他们加进队列。(注意,这样队列中可能会有重复的点,因为一个白点被多次更新权值可能会造成重复点的情况,所以每次取出白点时要判断这个白点的权值是否是最新的权值)

#include <cstdio>
#include <ctime>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

#define getmid(l,r) ((l) + ((r) - (l)) / 2)
#define MP(a,b) make_pair(a,b)
#define PB(a) push_back(a)

typedef long long ll;
typedef pair<int,int> pii;
const double eps = 1e-8;
const int INF = (1 << 30) - 1;
const int MAXN = 100010;

int n,m,k,ecnt;
int col[MAXN],num[MAXN],w[MAXN],anscol[MAXN];
int first[MAXN],enct;

struct node{
    double r;
    int w,id;
    node(double tr = 0,int tw = 0,int tid = 0) : r(tr) , w(tw) , id(tid) {}
    bool operator < (const node &b) const{
        return r > b.r;
    }
};

priority_queue<node> PQ;

struct edge{
    int v,next;
}e[MAXN << 1];

inline void add_edge(int u,int v){
    e[ecnt].next = first[u];
    e[ecnt].v = v;
    first[u] = ecnt++;
}

int main(){
    //Init
    memset(first,-1,sizeof(first));
    ecnt = 0;
    int a,b,c;
    scanf("%d%d%d",&n,&m,&k);
    for(int i = 1; i <= k; ++i){
        scanf("%d",&a);
        col[a] = 1;
    }
    for(int i = 1; i <= m; ++i){
        scanf("%d%d",&a,&b);
        add_edge(a,b);
        add_edge(b,a);
        num[a]++;
        num[b]++;
        if(!col[a]) w[b]++;
        if(!col[b]) w[a]++;
    }
    for(int i = 1; i <= n; ++i) if(!col[i]){ //非堡垒
        PQ.push(node(1.0 * w[i] / num[i],w[i],i));
    }
    double ans = -1;
    while(!PQ.empty()){
        node x = PQ.top(); PQ.pop();
        if(x.w != w[x.id]) continue;
        if(x.r > ans){
            ans = x.r;
            memcpy(anscol,col,sizeof(col));
        }
        col[x.id] = 1; //标黑
        for(int i = first[x.id]; ~i; i = e[i].next){
            int v = e[i].v;
            if(col[v]) continue;
            w[v]--;
            PQ.push(node(1.0 * w[v] / num[v],w[v],v));
        }
    }
    int cnt = 0;
    for(int i = 1; i <= n; ++i) if(!anscol[i]) ++cnt;
    printf("%d\n",cnt);
    for(int i = 1; i <= n; ++i) if(!anscol[i]){
        printf("%d ",i);
    }
    puts("");
    return 0;
}
View Code

 

 

 

 

posted @ 2015-06-26 22:36  Naturain  阅读(126)  评论(0编辑  收藏  举报