算是第一场xcpc(?)
现场出了的题:
E. Elevator 冷静分析以下就会发现其实贡献是前面的比当前数小的数和当前数的差值+1,后面的贡献是比当前小的数和当前数的差 于是预处理所有比当前数字小的数的和,前缀比当前数小的个数用树状数组维护一下就好 现场写线段树被卡掉了,光速改了一发树状数组才过的#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 5e5 + 5; ll t[N * 4], t1[N], t2[N]; int n, m, s[N], ss[N], a[N], cnt; map<int,int> ma; #define ls (x << 1) #define rs (x << 1) | 1 ll query(int x, int l, int r, int ql, int qr) { if (qr < ql) return 0; if (ql <= l && r <= qr) { return t[x]; } int mid = (l + r) >> 1; ll res = 0; if (ql <= mid) res += query(ls, l, mid, ql, qr); if (qr > mid) res += query(rs, mid + 1, r, ql, qr); return res; } void modify(int x, int l, int r, int p, ll v) { if (p < 1 || p > n) return; if (l == r) { t[x] += v; return; } int mid = (l + r) >> 1; if (p <= mid) modify(ls, l, mid, p, v); else modify(rs, mid + 1, r, p, v); t[x] = t[ls] + t[rs]; } const int mx = 5e5; int Tree[mx+10]; int Lowbit(int x){ return (x&(-x)); } void Add(int x, int nd){ for (int i = x ; i<= mx ; i +=Lowbit(i)){ Tree[i]+=nd; } } int GetAns(int x){ int ans = 0; for (int i = x ; i ; i -=Lowbit(i)) ans += Tree[i]; return ans; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &s[i]); ss[i] = s[i]; } sort(s + 1, s + n + 1); for (int i = 1; i <= n; i++) { if (s[i] != s[i - 1]) { ma[s[i]] = ++cnt; } // cerr << ma[s[i]] << endl; t1[cnt] += 1; t2[cnt] += s[i]; } for (int i = 1; i <= n; i++) { t1[i] += t1[i - 1]; t2[i] += t2[i - 1]; } for (int i = 1; i <= n; i++) { ll p = t1[ma[ss[i]]] - 1; ll q = t2[ma[ss[i]]] - ss[i]; ll res = p * ss[i] - q; long long r = GetAns(ma[ss[i]]); res += r; printf("%lld\n", res > m - 2 ? -1 : res); Add(ma[ss[i]],1); } return 0; }
H. GameX
发现一定是一个人1,3,5....这么填,另一个人2,4,6....这么填,每次填最小的没被填的数,最后比较谁填到的数字比较大即可。
因为$k$比较小所以直接模拟就好
#include <bits/stdc++.h> using namespace std; const int mx = 2e5+5; int T; int odd[mx],Even[mx]; int main(){ cin>>T; while (T--){ int cnt1 = 0,cnt2 = 0; int N,K; scanf("%d%d",&N,&K); for (int i = 1 ; i <= N ; i ++){ int x; scanf("%d",&x); if (x & 1) odd[++cnt1] = x; else Even[++cnt2] = x; } sort(odd+1,odd+cnt1+1); sort(Even+1,Even+cnt2+1); // for (int i = 1 ; i<=cnt1 ; i ++) // cout<<odd[i]<<" "; // cout<<endl; // for (int i = 1 ; i <=cnt2 ; i ++) // cout<<Even[i]<<" "; // cout<<endl; int pos = 0; int nw1 = 1; int KK = K; if ( odd[1] !=1 ) K--; else pos = 1; for (int i = 1 ; i <= K+1 ; i ++){ if(odd[pos] == 0 && pos != 0) { nw1+=2; continue; } while (nw1 + 2 == odd[pos+1]){ pos++; nw1 += 2; } nw1 += 2; //cout<<i<<" "<<nw1<<endl; } K = KK; int nw2 = 0; pos = 0; if (Even[1]!=0) K--; else pos = 1; for (int i = 1 ; i <= K+1 ; i ++){ if(pos != 1 && pos != 0 && Even[pos] == 0) nw2 +=2; while (nw2 + 2 == Even[pos+1]){ pos++; nw2 += 2; } nw2 += 2; //cout<<i<<" "<<nw2<<endl; } //cout<<nw1<<" "<<nw2<<endl; if (nw1 > nw2) printf("Alice\n"); else printf("Bob\n"); for (int i = 1 ; i <= cnt1 ; i ++) odd[i] = 0; for (int i = 1 ; i <= cnt2 ; i ++) Even[i] = 0; } return 0; }
L. Station of Fate
签到,没啥好说的(
#include <bits/stdc++.h> using namespace std; const int N = 1e5 + 5; const int NN = 1e5; const int MOD = 998244353; typedef long long ll; ll ny[N], jc[N]; ll fpow(ll x, int a) { ll res = 1; while (a) { if (a & 1) res = res * x % MOD; x = x * x % MOD; a >>= 1; } return res; } void init() { jc[0] = 1; for (int i = 1; i <= NN; i++) { jc[i] = jc[i - 1] * i % MOD; } ny[NN] = fpow(jc[NN], MOD - 2); for (int i = NN - 1; i >= 0; i--) { ny[i] = ny[i + 1] * (i + 1) % MOD; } } ll C(int a, int b) { return jc[a] * ny[b] % MOD * ny[a - b] % MOD; } int main() { int T; int n, m; init(); scanf("%d", &T); while (T--) { scanf("%d%d", &n, &m); printf("%lld\n", jc[n] * C(n - 1, m - 1) % MOD); } return 0; }
M. XOR Sum
首先$xor$肯定考虑拆位,不难发现拆位完之后,每个位置的贡献是$cnt_1$ * $cnt_0$,$cnt_1$表示$1$的个数,$cnt_0$表示$0$的个数。
然后考虑从高位往低位$dp$,有个问题是当前每个数字有一个不大于$m$的限制,对于这个限制,我们多记录一个变量表示当前有几个数还在被限制(数位$dp$的套路)
而高位往低位$dp$的时候,会遇到需要低位进位上来的情况,这种时候我们就把进位堆到低位处理,具体表现为每$dp$一位,进位的数量就要*2
这时候对于每一位,我们枚举这一位填几个$1$,其中有几个被限制的$1$,根据这个可以算出每次有几个数不再被限制,并且可以算出当前这一位会提供几个进位。
这样就可以记忆化出答案。
但是有个比较关键的剪枝:
如果后面所有位置能提供的进位加起来,都不够前面位数所需要的进位的话,就直接退出。
具体在代码里就表现为81*res<=K,$81$是因为显然, 在只有$18$个数字的前提下,9个1,9个0一定是最多的。
然后就过了(
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MOD = 1e9 + 7; const int M = 55 * 81; int K, sn[55], sm[55]; ll n, m; ll jc[20], ny[20], dp[55][55][M]; ll fpow(ll a, int b) { ll res = 1; while (b) { if (b & 1) res = res * a % MOD; a = a * a % MOD; b >>= 1; } return res; } ll C(int a, int b) { if (a < 0 || b < 0) return 0; if (b > a) return 0; if (b == 0) return 1; return jc[a] * ny[b] % MOD * ny[a - b] % MOD; } void init() { jc[0] = 1; memset(dp, -1, sizeof dp); for (int i = 1; i <= K; i++) { jc[i] = jc[i - 1] * i % MOD; } ny[K] = fpow(jc[K], MOD - 2); for (int i = K - 1; i >= 0; i--) { ny[i] = ny[i + 1] * (i + 1) % MOD; } ny[0] = 1; } ll dfs(ll x, int y, int k) { // y个受限制 需要k个进位 if (k < 0 || k > (x + 1) * 81) { return 0; } if (x < 0) { return 1; } if (dp[x][y][k] != -1) return dp[x][y][k]; ll res = 0; if (sm[x] == 1) { for (int i = 0; i <= K; i++) { // i个1 for (int j = 0, e = min(y, i); j <= e; j++) { // j个受限制的1 res = (res + dfs(x - 1, j, (k - i * (K - i) + sn[x]) * 2) * C(y, j) % MOD * C(K - y, i - j) % MOD) % MOD; } } } else { for (int i = 0; i <= K - y; i++) { res = (res + dfs(x - 1, y, (k - i * (K - i) + sn[x]) * 2) * C(K - y, i) % MOD) % MOD; } } return dp[x][y][k] = res; } int main() { scanf("%lld%lld%d", &n, &m, &K); for (ll i = 0, e = n; e; i++) { sn[i] = (e & 1); e >>= 1; } for (ll i = 0, e = m; e; i++) { sm[i] = (e & 1); e >>= 1; } init(); cout << dfs(52, K, 0) << endl; return 0; }
赛后补题:
I. Infection
现场想到了一个树上背包+换根+记录前后缀dp值的做法,但是没写完,噔噔咚(
$F[i][j]$表示已经选了初始感染点,在$i$点的子树里感染$j$个的概率,$G[i][j]$表示未选择初始感染点,在$i$子树内感染$j$个的概率
转移式很好写,具体代码直接树上背包即可。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MOD = 1e9 + 7; const int N = 2005; int n, e[N * 2], nex[N * 2], hed[N], ct, siz[N]; int a[N], sa, p[N], np[N], F[N][N], G[N][N], ans[N]; int fpow(ll a, int b) { ll res = 1; while (b) { if (b & 1) res = 1ll*res * a % MOD; a = 1ll*a * a % MOD; b >>= 1ll; } return res; } ll inv; void dfs(int x, int fa) { siz[x] = 1; F[x][1] = p[x]; G[x][1] = 1ll*a[x] * inv % MOD; for (int i = hed[x]; i; i = nex[i]) { if (e[i] == fa) continue; dfs(e[i], x); for (int j = siz[x] + siz[e[i]]; j >= 1; j--) { F[x][j] = 1ll*F[x][j] * np[e[i]] % MOD; G[x][j] = 1ll*G[x][j] * np[e[i]] % MOD; for (int k = max(j-siz[x],0), end = min(j - 1, siz[e[i]]); k <= end; k++) { F[x][j] = (F[x][j] + 1ll*F[e[i]][k] * F[x][j - k] % MOD) % MOD; G[x][j] = (G[x][j] + 1ll*F[e[i]][k] * G[x][j - k] % MOD) % MOD; G[x][j] = (G[x][j] + 1ll*G[e[i]][k] * F[x][j - k] % MOD) % MOD; } } siz[x] += siz[e[i]]; } for (int i = 1; i <= siz[x]; i++) { ans[i] = (ans[i] + 1ll*G[x][i] * np[fa] % MOD) % MOD; } } int main() { scanf("%d", &n); for (int i = 1; i < n; i++) { int a, b; scanf("%d%d", &a, &b); e[++ct] = b, nex[ct] = hed[a], hed[a] = ct; e[++ct] = a, nex[ct] = hed[b], hed[b] = ct; } for (int i = 1; i <= n; i++) { int b, c; scanf("%d%d%d", &a[i], &b, &c); p[i] = 1ll*b * fpow(c, MOD - 2) % MOD; np[i] = 1ll*(c - b) * fpow(c, MOD - 2) % MOD; sa = (sa + a[i]) % MOD; } inv = fpow(sa,MOD-2); np[0] = 1; dfs(1, 0); for (int i = 1; i <= n; i++) { printf("%d\n", ans[i]); } return 0; }
K. Middle Point Graph
因为点是随机的,所以四个点被随机成直接共面的概率是$0$
于是就可以开始分类讨论了
对于四元环来说,它的四个中点一定能构成一个平面。
对于三元环来说,选三个中点和随便一个顶点就可以构成一个平面
对于两个中点两个顶点的情况,有一类是在两个共点的边上,取 两个中点+两个顶点((a,b) 和 (b,c) 取 a,c和两个中点)
最后还有一种情况是把一条边的两个顶点和一个中点都取掉,再从剩下的点里面随便抓一个即可。
于是套个三四元环计数的板子即可。
#pragma GCC optimize(1) #pragma GCC optimize(2) #pragma GCC optimize(3,"Ofast","inline") #include<bits/stdc++.h> using namespace std; int T; int N,M; vector <int> G[400005]; long long read(){ long long ans=0; char last=' ',ch=getchar();//last????????????????????????????? while(ch<'0' || ch>'9')last=ch,ch=getchar();//???????????????????last?????ch???????? while(ch>='0' && ch<='9')ans=ans*10+ch-'0',ch=getchar();//?????????????????????? if(last=='-')ans=-ans;//??????? return ans; } inline void write(int x){ if (x < 0) x = ~x + 1, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } const long long fish=1e9+7; int cnt,nex[400005],las[400005],Arrive[400005],d[400005]; void jt(int x,int y){ cnt++; nex[cnt]=las[x]; las[x]=cnt; Arrive[cnt]=y; } bool cmp(int x,int y){ return (d[x]==d[y]?x>y:d[x]>d[y]); } int Pow(int x,int y){ int as=1; while (y){ if (y&1) as=1ll*as*1ll*x%fish; x=1ll*x*1ll*x%fish; y>>=1; } return as; } int inv3=333333336,inv4=250000002,inv2=500000004,inv24=41666667; int u[500005],v[500005]; int vis[500005],R3[500005],cnt4[500005],cnt3,ans4; void Get_3(){ for (int u=1;u<=N;u++){ for (auto v:G[u]) vis[v]++; for (auto v:G[u]) { for (auto w:G[v]){ if (vis[w]){ ++R3[w],++R3[u],++R3[v]; } } } for (auto v:G[u]) vis[v]=0; } for (int i=1;i<=N;i++) (cnt3+=R3[i])%=fish; cnt3=1ll*cnt3*1ll*inv3%fish; return; } void Get_4(){ for (int u=1;u<=N;u++){ for (auto v:G[u]){ if (cmp(u,v)){ for (auto w:G[v]){ if (cmp(u,w)){ cnt4[u]+=vis[w]; cnt4[v]+=vis[w]; cnt4[w]+=vis[w]; vis[w]++; } } } } for (auto v:G[u]){ if (cmp(u,v)){ for (auto w:G[v]){ if (cmp(u,w)){ cnt4[v]+=--vis[w]; } } } } } for (int i=1;i<=N;i++) (ans4+=cnt4[i])%=fish; ans4=1ll*ans4*1ll*inv4%fish; //cout<<ans4<<endl; return; } int Get(){//?????+????? int ans=0; for (int i=1;i<=N;i++){ (ans+=1ll*R3[i]*1ll*(d[i]-2)%fish)%=fish; } return ans; } void init(){ N=read();M=read(); int k; long long ans = 0; //k=read(); for (int i=1;i<=M;i++){ d[u[i]=read()]++,d[v[i]=read()]++; } long long inv2 = Pow(2,fish-2); for (int i = 1; i <= N ; i ++){ (ans += 1ll * d[i] *(d[i]-1)%fish * inv2%fish)%=fish; } for (int i=1;i<=M;i++) if (cmp(u[i],v[i])) G[u[i]].push_back(v[i]); else G[v[i]].push_back(u[i]); Get_3(); ans = (ans + 3ll*cnt3%fish)%fish; for (int i=1;i<=M;i++) if (cmp(u[i],v[i])) G[v[i]].push_back(u[i]); else G[u[i]].push_back(v[i]); Get_4(); ans = (ans + ans4)%fish; ans = (ans + 1ll*M*(N+M-3)%fish)%fish; printf("%lld\n",ans); return; } void Clear(){ cnt=0; for (int i=1;i<=N;i++){ R3[i]=cnt4[i]=vis[i]=d[i]=0; G[i].clear(); } ans4=0;cnt3=0; } int main(){ T=read(); while (T--){ Clear(); init(); } return 0; }
标签:广州,return,2022ccpc,int,res,ll,long,MOD From: https://www.cnblogs.com/si--nian/p/16895497.html