NOIP模拟48
T1:
考虑先将完全图转化为数学语言,即完全状态(全排列)
容易发现m条删边实际上是限制条件,那么利用容斥解决限制
条件的思路较容易想到,于是问题转化为容斥m条删边所损失
的环数。
裸容斥,考虑首先完全图下容易想到合法环为(n-1)!/2,
考虑抽象为数学语言,每个点代表序列上的一个点,那么问题
实际转化为排列数,考虑正确性,完全状态下并不需要考虑排
列合法性(全部合法),那么由于要求成环并且顺序不能相同,
显然为项链数(圆排列/2)。
考虑首先减去包含一条所删边,那么必然多减了同时包含
两条所删边的情况,归纳出奇减偶加。
这里首先给出二项式反演与子集反演的特点,对于本题而
言首先m范围很小那么对于子集反演枚举状态可以承受,其次
对于环上节点数相同的状态显然具有不同结果,需要根据情况
判断,因此需要子集反演。
二项式反演具有优秀的时间复杂度(通过组合数代替子集
枚举)然而其缺点在与必须满足同一组合具有相同的性质,即
同一组合的计算方式相同(这也是其可以枚举组合数的原因)
对应的子集反演时间复杂度较劣(2^n),然而其优点在于
并不在意组合内的性质差异,类似本题,对于环上节点数相同的
状态具有不同的计算方式,而子集枚举就能很好的针对性计算。
回到本题考虑如何计算包含x条所删边的环数,考虑首先将
状态x中每一条链缩为一个点(目的是降低复杂度),类似的问
题转化为存在多少链包含当前点集,那么根据之前的分析,答案
为(n-|s|-1)!/2,然而不完全正确,考虑显然需要考虑将之前的缩点
造成的影响,发现假设存在一条竖直方向的链,显然从上往下排
列与从下往上排列是不同的,然而缩点后其失去了原来的矢量性
所以设当前状态链数为k,则答案为(n-|s|-1)!*2^(k-1)(每条链都有
两种方向可选)
发现问题似乎结束了,然而考虑状态定义:包含x条边的环数
显然并不是所有情况都能根据公式计算,需要删除不可能成环的
方案(因为公式中的参数显然未考虑不合法情况,只是单纯根据
边数计算),发现若存在点度数大于2或成环但环数大小不为n的
情况数为0(对于大小为n的环显然不可能满足上述条件),利用
并查集判断即可。
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 #define B bool 6 #define V void 7 #define LL long long 8 #define P pair<I,I> 9 #define MP make_pair 10 #define lowbit(x) (x & -x) 11 const I N = 1e7 + 3; 12 const I mod = 1e9 + 7; 13 I n,m,ans,num[1 << 20],J[N]; 14 vector <P> edge; 15 inline I read() { 16 I x(0),y(1); C z(getchar()); 17 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); } 18 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar(); 19 return x * y; 20 } 21 inline I qpow (I a,I b) { I res(1); 22 for (; b ;a = (LL)a * a % mod, b >>= 1) 23 if (b & 1) res = (LL)res * a % mod; 24 return res; 25 } 26 struct DJS { 27 I f[N],size[N],times[N],num; 28 B jud[N]; 29 queue <I> q; 30 inline V initital () { for (I i(1);i <= n; ++ i) f[i] = i, size[i] = 1; } 31 I get (I x) { return x == f[x] ? x : f[x] = get(f[x]); } 32 inline I merge (I x,I y) { 33 I fx(get(x)), fy(get(y)); 34 if (fx == fy) return size[fx]; 35 if ( jud[fx]) jud[fx] = 0, num -- ; 36 if (!jud[fy]) jud[fy] = 1, num ++ ; 37 f[fx] = fy; size[fy] += size[fx]; q.push(x), q.push(y); 38 return 0; 39 } 40 inline V clear () { 41 num = 0; 42 while (!q.empty ()) { 43 I x(q.front()); q.pop(); 44 f[x] = x; size[x] = 1; 45 times[x] = 0; jud[x] = 0; 46 } 47 } 48 }DJS; 49 inline V solve (I s) { 50 DJS.clear (); I size; 51 if (num[s] > n) return ; 52 for (I i(0);i < edge.size(); ++ i) if (s & 1 << i) { 53 size = DJS.merge (edge[i].first,edge[i].second); 54 if (++ DJS.times[edge[i].first] > 2) return ; 55 if (++ DJS.times[edge[i].second] > 2) return ; 56 if (size) if (size == n) { num[s] & 1 ? ans -- : ans ++ ; return ; } else return ; 57 } 58 I tmp = (LL)J[n - num[s] - 1] * (1 << -- DJS.num) % mod; 59 num[s] & 1 ? ans = (ans - tmp) % mod : ans = (ans + tmp) % mod; 60 } 61 signed main () { 62 //freopen ("data.in","r",stdin); freopen ("my.out","w",stdout); 63 n = read(), m = read(); DJS.initital (); 64 J[0] = 1; for (I i(1);i <= n; ++ i) J[i] = (LL)J[i - 1] * i % mod; 65 for (I i(1);i <= m; ++ i) { 66 I x(read()),y(read()); if (x == y) continue; 67 edge.push_back (MP(x,y)); 68 } 69 ans = (LL)J[n - 1] * qpow (2,mod - 2) % mod; 70 for (I i(1),tmp(1 << edge.size());i < tmp; ++ i) 71 num[i] = num[i - lowbit(i)] + 1, solve (i); 72 printf ("%d\n",(ans + mod) % mod); 73 }
T3:
题目描述显然考虑二分答案转判定,考虑判定在与是否存在合
法方案满足块数小于m,考虑DP,因为只有两行,与是分类讨论分
别在1,2,(1,2)行放置矩形。
那么定义DP[i]为放置到第i列所需最少画数,考虑如何转移,首
先若上一次在(1,2)放置,那么画数加一即可,考虑对于在1,2
放置,不断枚举回溯几次决策取最小值即可,实际上本题的本质是背
包DP变形。
注意因为要保持最优性决策,预处理1,2,(1,2)每一块画
最多覆盖范围即可。
代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define I int 4 #define C char 5 #define B bool 6 #define LL long long 7 const I N = 1e5 + 3; 8 I n,m,nxt1[N],nxt2[N],nxt3[N],dp[N]; 9 LL s1[N],s2[N],s3[N]; 10 inline I read() { 11 I x(0),y(1); C z(getchar()); 12 while (!isdigit(z)) { if (z == '-') y = -1; z = getchar(); } 13 while ( isdigit(z)) x = x * 10 + (z ^ 48), z = getchar(); 14 return x * y; 15 } 16 B check (LL x) { 17 for (I i(1);i <= n; ++ i) { 18 nxt1[i] = nxt1[i - 1], nxt2[i] = nxt2[i - 1], nxt3[i] = nxt3[i - 1]; 19 while (s1[i] - s1[nxt1[i]] > x) nxt1[i] ++ ; 20 while (s2[i] - s2[nxt2[i]] > x) nxt2[i] ++ ; 21 while (s3[i] - s3[nxt3[i]] > x) nxt3[i] ++ ; 22 } 23 for (I i(1);i <= n; ++ i) { 24 I p1(i),p2(i),s(0); 25 dp[i] = dp[nxt3[i]] + 1; 26 while (++ s <= m && (p1 || p2)) { 27 p1 > p2 ? p1 = nxt1[p1] : p2 = nxt2[p2]; 28 dp[i] = min(dp[i],dp[max (p1,p2)] + s); 29 } 30 if (dp[i] > m) return false; 31 } 32 return true; 33 } 34 signed main () { 35 I MA(INT_MIN); LL S(0); 36 n = read(), m = read(); 37 for (I i(1),x;i <= n; ++ i) S += x = read(), s1[i] = s1[i - 1] + x, MA = max (MA,x); 38 for (I i(1),x;i <= n; ++ i) S += x = read(), s2[i] = s2[i - 1] + x, MA = max (MA,x); 39 for (I i(1);i <= n; ++ i) s3[i] = s1[i] + s2[i]; 40 LL l(MA),r(S); 41 while (l < r) { LL mid(l + r >> 1); 42 check (mid) ? r = mid : l = mid + 1; 43 } 44 printf ("%lld\n",l); 45 }