2021/01/04训练记录
衔尾蛇
https://ac.nowcoder.com/acm/contest/9854/D
@[toc]
P1446 [HNOI2008]Cards
题目链接:P1446 [HNOI2008]Cards
题目大意:给定r张红牌,g张绿牌,b张蓝牌,再给定m个洗牌方式,求本质不同的牌摆列方式有多少个。两种摆列方式是一样的是指一种排列方式可以通过一次洗牌变成另一种。
数据范围:
题解:考虑使用Burnside引理。本质不同的方案数为在每个置换下稳定不动的方案平均值。每一个置换都可以表示为多个小置换(或者叫环?群论里面的东西),要保证环内元素在置换后稳定不动,即同一个环的卡牌颜色要是相同的。所以我们大致思路就是,对每一个置换求出环数与每个环的长度,再去考虑每一个环上的卡牌颜色。这里我们使用来解决。表示使用个红牌,个绿牌,个蓝牌的方案数。其实就是一个背包了。具体看代码吧。
AC代码:
#include<bits/stdc++.h> #define ld long double #define ll long long using namespace std; template<class T> void read(T& x) { T res = 0, f = 1; char c = getchar(); while (!isdigit(c)) { if (c == '-')f = -1; c = getchar(); } while (isdigit(c)) { res = (res << 3) + (res << 1) + c - '0'; c = getchar(); } x = res * f; } const ll N = 100 + 10; int ans = 0; int r, b, g, m, mod,n,a[N],f[N][N][N]; int cnt[N], vis[N]; void solve() { for (int i = 0; i <= r; i++) for (int j = 0; j <= b; j++) for (int k = 0; k <= g; k++) f[i][j][k] = 0; f[0][0][0] = 1; memset(cnt, 0, sizeof(cnt)); memset(vis, 0, sizeof(vis)); for (int i = 1; i <= n; i++) { if (vis[i])continue; vis[i] = 1; int x = a[i]; cnt[0]++; cnt[cnt[0]] = 1; while (!vis[x]) { vis[x] = 1; x = a[x]; cnt[cnt[0]]++; } } for(int i=1;i<=cnt[0];i++) for(int j=r;j>=0;j--) for(int k=b;k>=0;k--) for (int l = g; l >= 0; l--) { if (j >= cnt[i])f[j][k][l] = (f[j][k][l] + f[j - cnt[i]][k][l]) % mod; if (k >= cnt[i])f[j][k][l] = (f[j][k][l] + f[j][k - cnt[i]][l]) % mod; if (l >= cnt[i])f[j][k][l] = (f[j][k][l] + f[j][k][l - cnt[i]]) % mod; } ans = (ans + f[r][b][g]) % mod; } int fpow(int x, int y) { int ans = 1; while (y) { if (y & 1)ans = ans * x % mod; x = x * x % mod; y >>= 1; } return ans; } int main() { //ios::sync_with_stdio(false); #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif // ONLINE_JUDGE read(r), read(b), read(g), read(m), read(mod); n = r + b + g; for (int i = 1; i <= n; i++)a[i] = i; solve(); for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { read(a[j]); } solve(); } printf("%d\n", ans * fpow(m+1, mod - 2)%mod); return 0; }
牛客2020跨年场D-衔尾蛇
题目链接:D-衔尾蛇
题目大意:一共有条红蛇,条蓝蛇,条绿蛇。她们想知道一共可以形成多少种不同的衔尾蛇的环?(蛇可以不用完)
数据范围:
题解:终于到了这两天问题的起点,一看,当时直接想着丢一个爆搜上去,不过写了2小时还是没调出来,然后去学了一圈 定理和引理,有点杀鸡用牛刀了。不过引理可以很好这一类本质不同问题,而且时间复杂度很良好。
好了,我们考虑用引理来解决这题,我们不妨考虑条红蛇,条蓝蛇,条绿蛇可以组成多少本质不同的环(要求全用完)。令,因为我们可以选择旋转环,比如顺时针旋转1格,2格...。所以这里相当于有着n个置换,其中第个置换中,第1个位置->第i个位置,然后以此类推。本质不同的方案数为在每个置换下稳定不动的方案平均值。每一个置换都可以表示为多个小置换(或者叫环?群论里面的东西),要保证环内元素在置换后稳定不动,即同一个小环的蛇颜色要是相同的。考虑第个置换,有着个小置换,每一个置换长度为,如果每一个小置换都可以被同一种颜色的蛇填充,那么答案就加上.具体看代码吧。
AC代码:
#include<bits/stdc++.h> #define ld long double #define ll long long using namespace std; template<class T> void read(T& x) { T res = 0, f = 1; char c = getchar(); while (!isdigit(c)) { if (c == '-')f = -1; c = getchar(); } while (isdigit(c)) { res = (res << 3) + (res << 1) + c - '0'; c = getchar(); } x = res * f; } const ll N = 200000 + 10; const int mod = 1e9 + 7; ll a, b, c, f[N]; void init() { f[0] = 1; for (int i = 1; i <= 15; i++) { f[i] = f[i - 1] * i; } } ll C(int x, int y) { return f[x] / (f[y] * f[x - y]); } ll gcd(int x, int y) { return y ? gcd(y, x % y) : x; } ll calc(ll a, ll b, ll c) { ll res = 0; for (int i = 1; i <= a + b + c; i++) { int numh = gcd(a + b + c, i); int len = (a + b + c) / numh; if (a % len || b % len || c % len)continue; res += C(numh, a / len) * C(numh - a / len, b / len); } return res / (a + b + c); } int main() { //ios::sync_with_stdio(false); #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif // ONLINE_JUDGE read(a); read(b), read(c); init(); int ans = 0; for (int i = 0; i <= a; i++) for (int j = 0; j <= b; j++) for (int k = 0; k <= c; k++) { if (i == 0 && j == 0 && k == 0)continue; ans += calc(i, j, k); } printf("%d\n", ans); return 0; }
牛客2020跨年场H-牛清楚的裙子!!!
题目链接:H-牛清楚的裙子!!!
题目大意:牛清楚有n条裙子,穿每条裙子的概率一样为。其中穿1号裙加10000分,其他裙子加1分。询问当每条裙子都穿过的时候,期望的得分是多少。t次询问。
数据范围:dp,就可以快速求出答案了。
AC代码:
#include<bits/stdc++.h> #define ld long double #define ll long long using namespace std; template<class T> void read(T& x) { T res = 0, f = 1; char c = getchar(); while (!isdigit(c)) { if (c == '-')f = -1; c = getchar(); } while (isdigit(c)) { res = (res << 3) + (res << 1) + c - '0'; c = getchar(); } x = res * f; } const ll N = 10000000 + 10; const int mod = 1e9 + 7; int t, n; double dp[N]; int main() { //ios::sync_with_stdio(false); #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif // ONLINE_JUDGE for (int i = 1; i <= 1e7; i++) { dp[i] = dp[i - 1] + 1.0 / i; } read(t); while (t--) { read(n); double ans = (n + 9999)* dp[n]; printf("%.7lf\n", ans); } return 0; }
牛客2020跨年场F-开心消消乐
题目链接:F-开心消消乐
题目大意:给定一个01字符串s,可以消去数字1个数为数字0个数*2的一段,再进行01消除,即0在前1在后且相邻即可消除。问剩下的字符串最短长度。
数据范围:
题解:瞧一眼数据范围,可以过去,考虑枚举删去的区间,然后将剩下的和拼接起来。对于的一段,我们可以用栈来模拟消去,可以得到消除的次数。并且栈中的元素必然是这一类的。用表示中删去的次数,表示中删去的次数(这个是倒着做)。表示对应栈中剩余的个数。对应模拟一下就出来了。
AC代码:
#include<bits/stdc++.h> #define ld long double #define ll long long using namespace std; template<class T> void read(T& x) { T res = 0, f = 1; char c = getchar(); while (!isdigit(c)) { if (c == '-')f = -1; c = getchar(); } while (isdigit(c)) { res = (res << 3) + (res << 1) + c - '0'; c = getchar(); } x = res * f; } const ll N = 200000 + 10; const int mod = 1e9 + 7; int n,sum[N],num[N][2],pre[N],rpre[N]; char s[N]; void init() { for (int i = 1; i <= n; i++)sum[i] = sum[i - 1] + s[i] - '0'; stack<int>pls; for (int i = 1; i <= n; i++) { pre[i] = pre[i - 1]; if (!pls.size()) { pls.push(i); num[i][0] = s[i] == '0'; continue; } if (s[i] == '1' && s[pls.top()] == '0') { pls.pop(); pre[i]++; if (pls.size())num[i][0] = num[pls.top()][0]; else num[i][0] = 0; continue; } if (pls.size())num[i][0] = num[pls.top()][0] + (s[i] == '0'); else num[i][0] = (s[i] == '0'); pls.push(i); } while (pls.size())pls.pop(); for (int i = n; i >= 1; i--) { rpre[i] = rpre[i + 1]; if (!pls.size()) { pls.push(i); num[i][1] = (s[i] == '1'); continue; } if (s[i] == '0' && s[pls.top()] == '1') { pls.pop(); rpre[i]++; if (pls.size())num[i][1] = num[pls.top()][1]; else num[i][1] = 0; continue; } if (pls.size())num[i][1] = num[pls.top()][1] + (s[i] == '1'); else num[i][1] = (s[i] == '1'); pls.push(i); } } int calc(int x,int l,int r) { if (x == 1) { return sum[r] - sum[l - 1]; } else { return (r - l + 1) - (sum[r] - sum[l - 1]); } } int find(int l, int r) { if (calc(1,l,r) != 2 * calc(0,l,r))return n-pre[n]*2; int al = pre[l - 1] + rpre[r + 1]; int al2 = min(num[l - 1][0], num[r + 1][1]); return n - (r - l + 1) - (al + al2) * 2; } int main() { //ios::sync_with_stdio(false); #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif // ONLINE_JUDGE read(n); scanf("%s", s + 1); init(); int ans = n-pre[n]*2; for(int l=1;l<=n;l++) for (int r = l; r <= n; r++) { ans = min(ans,find(l, r)); } printf("%d\n", ans); return 0; }
牛客2020跨年场G-CoolCool的序列
题目链接:G-CoolCool的序列
题目大意:给定一个长度为的序列,要求翻转序列,其中交换和花费,问最小花费。
数据范围:
题解:考虑对相同的数进行变换,即两个位置数组进行变换,要求距离差最小,直接排序即可
AC代码:
#include<bits/stdc++.h> #define ld long double #define ll long long using namespace std; template<class T> void read(T& x) { T res = 0, f = 1; char c = getchar(); while (!isdigit(c)) { if (c == '-')f = -1; c = getchar(); } while (isdigit(c)) { res = (res << 3) + (res << 1) + c - '0'; c = getchar(); } x = res * f; } #define int long long const ll N = 200000 + 10; const int mod = 1e9 + 7; int n, a[N]; vector<int>w[N], to[N]; signed main() { //ios::sync_with_stdio(false); #ifndef ONLINE_JUDGE freopen("test.in", "r", stdin); #endif // ONLINE_JUDGE read(n); for (int i = 1; i <= n; i++) { read(a[i]); w[a[i]].push_back(i); to[a[i]].push_back(n - i + 1); } int ans = 0; for (int i = 1; i <= 1e5; i++) { sort(w[i].begin(), w[i].end()); sort(to[i].begin(), to[i].end()); for (int j = 0; j < w[i].size(); j++) { ans += abs(to[i][j] - w[i][j]); } } printf("%lld\n", ans / 2); return 0; } ``