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