Preface
事实证明比赛前真得训练一下,不然正赛的时候疯狂发病真顶不住
本来想周四晚上 VP 下杭州或者上海,但好像都没上 QOJ,遂找了这赛季的 CCPC 重庆聊以慰藉
本来以为计数场要寄了的,结果最后徐神绝杀 4:59 绝杀 D 之后也是来到 9 题区,感觉也没这么毒瘤
A. 乘积,欧拉函数,求和
很典的一个题,和暑假牛客多校的一个题几乎一样
由于欧拉函数 \(\phi(n)=n\times \prod \frac{p_i-1}{p_i}\),因此一个集合的贡献可以分成两部分,一个是直接乘积起来的部分,一个是后面对每个质因子只求一次贡献的部分
考虑令 \(f(i,mask)\) 表示处理了前 \(i\) 个数,此时出现过的质因数集合的状压值为 \(mask\) 时,前面部分的贡献是多少,转移就是个背包
直接做复杂度肯定不能接受,根据经典套路 \(\sqrt {3000}\) 以内的质数只有 \(16\) 个,因此我们可以只状压较小的质因子
之后将所有数按照最大质因子分组,显然同一组内的贡献可以一起讨论,总复杂度 \(O(n\times 2^{16})\)
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=3005,all=1<<16,mod=998244353;
int n,a[N],vis[N],pri[N],id[N],cnt,f[2][1<<16];
inline void inc(int& x,CI y)
{if ((x+=y)>=mod) x-=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
struct ifo
{int val,S;inline ifo(CI VAL=0,CI NS=0){val=VAL; S=NS;}
}; vector <ifo> vec[N];
inline void init(CI n)
{for (RI i=2;i<=n;++i){if (!vis[i]) id[i]=cnt,pri[cnt++]=i;for (RI j=0;j<cnt&&i*pri[j]<=n;++j)if (vis[i*pri[j]]=1,i%pri[j]==0) break;}
}
int main()
{init(3000); scanf("%d",&n);for (RI i=1;i<=n;++i){scanf("%d",&a[i]);ifo cur(a[i],0);for (RI j=0;j<16;++j)if (a[i]%pri[j]==0){cur.S|=(1<<j);while (a[i]%pri[j]==0) a[i]/=pri[j];}if (a[i]>1) vec[id[a[i]]].push_back(cur);else vec[0].push_back(cur);}f[0][0]=1;for (auto [val,S]:vec[0]){for (RI mask=0;mask<all;++mask) f[1][mask]=f[0][mask];for (RI mask=0;mask<all;++mask) inc(f[1][mask|S],1LL*f[0][mask]*val%mod);for (RI mask=0;mask<all;++mask) f[0][mask]=f[1][mask];}for (RI mask=0;mask<all;++mask) f[1][mask]=0;for (RI i=16;i<cnt;++i){if (vec[i].empty()) continue;for (auto [val,S]:vec[i]){static int g[2][1<<16];for (RI mask=0;mask<all;++mask) g[0][mask]=f[0][mask],g[1][mask]=f[1][mask];for (RI mask=0;mask<all;++mask){inc(g[1][mask|S],1LL*f[1][mask]*val%mod);inc(g[1][mask|S],1LL*f[0][mask]*(val*(pri[i]-1)/pri[i])%mod);}for (RI mask=0;mask<all;++mask) f[0][mask]=g[0][mask],f[1][mask]=g[1][mask];}for (RI mask=0;mask<all;++mask) inc(f[0][mask],f[1][mask]),f[1][mask]=0;}int ans=0;for (RI mask=0;mask<all;++mask){int cur=f[0][mask];for (RI i=0;i<16;++i)if ((mask>>i)&1) cur=1LL*cur*(pri[i]-1)%mod*quick_pow(pri[i])%mod;inc(ans,cur);}return printf("%d",ans),0;
}
B. osu!mania
签到,注意对浮点数的处理尽可能不要丢精度
#include<cstdio>
#include<iostream>
#include<cmath>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,ppmax,a,b,c,d,e,f;
signed main()
{for (scanf("%lld",&t);t;--t){scanf("%lld%lld%lld%lld%lld%lld%lld",&ppmax,&a,&b,&c,&d,&e,&f);int Acc=round(1.0L*(10000*(300*a+300*b+200*c+100*d+50*e))/(300*(a+b+c+d+e+f)));int A=Acc/100,B=Acc%100; printf("%lld.",A);if (B<10) printf("0%lld%% ",B); else printf("%lld%% ",B);int up=320*a+300*b+200*c+100*d+50*e,dn=320*(a+b+c+d+e+f);// up/dn<=4/5if (5*up<=4*dn) printf("0\n"); else{//up/dn-4/5=(5*up-4*dn)/(5*dn)int pp=round(1.0L*(5*ppmax*(5*up-4*dn))/(5*dn));printf("%lld\n",pp);}}return 0;
}
C. 连方
徐神开场写的,我题目都没看
#include <bits/stdc++.h>void work() {int n; std::cin >> n;std::string a, b; std::cin >> a >> b;std::string full(n, '#'), empty(n, '.'), ans[7];if(a == full && b == full) {std::cout << "Yes\n";for(int i = 0; i < 7; ++i) std::cout << full << char(10);return ;}if(a == empty || b == empty) {std::cout << "Yes\n";if(a == empty) for(int i = 0; i < 7; ++i) std::cout << b << char(10);else for(int i = 0; i < 7; ++i) std::cout << a << char(10);return ;}if(a == full || b == full) {std::cout << "No\n";return ;}ans[0] = a, ans[6] = b;for(int i = 1; i < 6; ++i) ans[i] = empty;int l, r;for(int i = 0; i < n; ++i) {if(a[i] == '#') ans[1][i] = '.'; else ans[1][i] = '#';if(b[i] == '#') ans[5][i] = '.'; else ans[5][i] = '#';}for(int i = 0; i < n; ++i) {if(ans[1][i] == '#') continue;if(i > 0 && ans[1][i - 1] == '#' || i < n - 1 && ans[1][i + 1] == '#') {ans[2][l = i] = '#';break;}}for(int i = 0; i < n; ++i) {if(ans[5][i] == '#') continue;if(i > 0 && ans[5][i - 1] == '#' || i < n - 1 && ans[5][i + 1] == '#') {ans[4][r = i] = '#';break;}}if(l > r) std::swap(l, r);if(std::abs(l - r) <= 1) ans[3][l] = '#'; else {for(int i = l + 1; i <= r - 1; ++i) ans[3][i] = '#';}std::cout << "Yes\n";for(int i = 0; i < 7; ++i) std::cout << ans[i] << char(10);return ;
}int main() {std::ios::sync_with_stdio(false);int T; std::cin >> T; while(T--) work();return 0;
}
D. 有限小数
徐神和祁神一直在讨论这个题,我当时在想+写 H 就没咋参与
本来一直 WA 最后神秘改了下枚举的上界就过了,十分神奇
#include <bits/stdc++.h>using i128 = __int128_t;
using llsi = long long;constexpr int c2 = 40, c5 = 20;template <typename T>
T myabs(T a) {return a < 0 ? -a : a;
}i128 p2[101] = {1}, p5[101] = {1};
i128 w;i128 exgcd(i128 a, i128 b, i128 &x, i128 &y) {if (0==b) { x = 1; y = 0; return a; }i128 ret = exgcd(b, a % b, y, x);y -= a / b * x;return ret;
}i128 calc(i128 a, i128 b, i128 c) {i128 x0, y0;i128 g = exgcd(a, b, x0, y0);// assert(a * x0 + b * y0 == g);if (c % g != 0) return -1;x0 *= c / g, y0 *= c / g;i128 val = myabs(b / g);x0 = (x0 % val + val) % val;// if (0 == x0) x0 = val;return x0;
}void work() {llsi _a, _b; std::cin >> _a >> _b;i128 a = _a, b = _b;llsi c, d = -1;for(int x = 0; x <= c2; ++x) for(int y = 0; y <= c5; ++y) {if(b * w % (p2[x] * p5[y]) != 0) break;i128 nd = b * w / (p2[x] * p5[y]), nc = calc(- p2[x] * p5[y], b, a * w);if(nc < 0) continue;if(nd > 1'000'000'000) continue;// std::cerr << std::format("x = {}, y = {}, c = {}, d = {}\n", p2[x], p5[y], nc, nd);if(d < 0 || nc < c || nc == c && nd < d) c = nc, d = nd;}std::cout << c << " " << d << std::endl;
}int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr), std::cout.tie(nullptr);for(int i = 1; i <= std::max(c2, c5); ++i) p2[i] = p2[i - 1] * 2, p5[i] = p5[i - 1] * 5;w = p2[30] * p5[13];// std::cout << i128(w) << char(10);int T; std::cin >> T; while(T--) work();return 0;
}
E. 合成大西瓜
手玩一波会发现答案就是所有度数不为一的点权值的最大值;以及所有度数为一的点权值的次大值;再求一个 \(\max\) 即可
#include<bits/stdc++.h>
using namespace std;const int N = 1e5+5;
int n, m, A[N], deg[N], mx2, mx1, sd1;
signed main() {ios::sync_with_stdio(0); cin.tie(0);cin >> n >> m;for (int i=1; i<=n; ++i) cin >> A[i];for (int i=1; i<=m; ++i) {int x, y; cin >> x >> y;++deg[x], ++deg[y];}if (1==n) {cout << A[1] << '\n';return 0;}mx2 = mx1 = sd1 = -1;for (int i=1; i<=n; ++i) {if (1==deg[i]) {if (A[i]>=mx1) sd1 = mx1, mx1 = A[i];else if (A[i]>sd1) sd1 = A[i];} else {mx2 = max(mx2, A[i]);}}cout << max(mx2, sd1) << '\n';return 0;
}
H. str(list(s))
想清楚了其实很好写,注意到只有 '
是特殊的,因此我们需要特别记录 ‘
的个数
令 \(f_{i,j}\) 表示 \(s^i\) 下标模 \(p\) 后为 \(j\) 的所有非 '
字符的贡献;\(g_{i,j}\) 表示 \(s^i\) 下标模 \(p\) 后为 \(j\) 的 '
数量;\(num_{i,j}\) 表示 \(s^i\) 下标模 \(p\) 后为 \(j\) 的所有字符的数量
转移的时候按照题目模拟一下即可,注意特判开头和结尾的变化,复杂度 \(O(|S|+kp)\)
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,M=3005,mod=1e9+7;
char s[N]; int k,p,f[M][M],g[M][M],num[M][M];
inline void inc(int& x,CI y)
{if ((x+=y)>=mod) x-=mod;
}
int main()
{scanf("%s%d%d",s,&k,&p);int n=strlen(s);for (RI i=0;i<n;++i){++num[0][i%p];if (s[i]=='\'') ++g[0][i%p]; else inc(f[0][i%p],s[i]);}n=(n-1+p)%p;for (RI i=0;i<k;++i){inc(f[i+1][0],(1LL*(num[i][0]-1+mod)%mod*' '%mod+'[')%mod);inc(f[i+1][(5*n+4)%p],(1LL*(num[i][n]-1+mod)%mod*','%mod+']')%mod);for (RI j=0;j<p;++j){inc(f[i+1][(5*j+2)%p],f[i][j]);// inc(f[i+1][(5*j+1)%p],1LL*(num[i][j]-g[i][j]+mod)*'\''%mod);// inc(f[i+1][(5*j+3)%p],1LL*(num[i][j]-g[i][j]+mod)*'\''%mod);inc(g[i+1][(5*j+1)%p],(num[i][j]-g[i][j]+mod)%mod);inc(g[i+1][(5*j+3)%p],(num[i][j]-g[i][j]+mod)%mod);if (j!=0) inc(f[i+1][(5*j+0)%p],1LL*num[i][j]*' '%mod);if (j!=n) inc(f[i+1][(5*j+4)%p],1LL*num[i][j]*','%mod);}for (RI j=0;j<p;++j){inc(g[i+1][(5*j+2)%p],g[i][j]);inc(f[i+1][(5*j+1)%p],1LL*g[i][j]*'"'%mod);inc(f[i+1][(5*j+3)%p],1LL*g[i][j]*'"'%mod);}for (RI j=0;j<p;++j) for (RI k=0;k<5;++k)inc(num[i+1][(5*j+k)%p],num[i][j]);n=(5*n+4)%p;}for (RI i=0;i<p;++i)printf("%d ", int((f[k][i]+1LL*g[k][i]*'\''%mod)%mod));// printf("%c", char((f[k][i]+1LL*g[k][i]*'\''%mod)%mod));return 0;
}
I. 算术
注意到除了 \(1\) 之外的所有卡片一定是直接乘起来最优,现在的问题就是 \(1\) 要怎么使用
一种是拿两个 \(1\) 凑出一个新的 \(2\);另一种是不停地把剩下的 \(1\) 加到做乘法的数中最小的上面
由于 \(a_i\le 100\),我们可以直接枚举新生成了多少个 \(2\),剩下的部分模拟一下找个最优的方案即可
现在的问题是怎么比较两种方案的大小,由于式子是连乘的形式因此直接取对数后相加即可
#include<cstdio>
#include<iostream>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
const int N=15,mod=998244353;
int t,a[N],b[N]; long double lg[105];
int main()
{for (RI i=1;i<=100;++i) lg[i]=logl(i);for (scanf("%d",&t);t;--t){for (RI i=1;i<=9;++i) scanf("%d",&a[i]);long double mx=-1; int cs;for (RI i=0;2*i<=a[1];++i){for (RI j=1;j<=9;++j) b[j]=a[j];b[1]-=2*i; b[2]+=i;for (RI j=2;j<9&&b[1];++j){int tmp=min(b[1],b[j]);b[1]-=tmp; b[j]-=tmp; b[j+1]+=tmp;}long double cur=0;if (b[1]>0){if (b[9]==0) cur=0; else{int add=b[1]/b[9],rem=b[1]%b[9];cur+=lg[9+add+1]*rem;cur+=lg[9+add]*(b[9]-rem);}} else{for (RI j=2;j<=9;++j) cur+=lg[j]*b[j];}if (cur>mx) mx=cur,cs=i;}for (RI i=1;i<=9;++i) b[i]=a[i];b[1]-=2*cs; b[2]+=cs;for (RI i=2;i<9&&b[1];++i){int tmp=min(b[1],b[i]);b[1]-=tmp; b[i]-=tmp; b[i+1]+=tmp;}int ans=1;if (b[1]>0){if (b[9]==0) ans=1; else{int add=b[1]/b[9],rem=b[1]%b[9];for (RI i=1;i<=rem;++i) ans=1LL*ans*(9+add+1)%mod;for (RI i=1;i<=b[9]-rem;++i) ans=1LL*ans*(9+add)%mod;}} else{for (RI i=2;i<=9;++i)for (RI j=1;j<=b[i];++j) ans=1LL*ans*i%mod;}printf("%d\n",ans);}return 0;
}
J. 骰子
纯签到,直接 Gauss 一定每个面都能凑到 \(6\) 即可
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int n,m;
int main()
{scanf("%d%d",&n,&m);return printf("%d",6*n*m),0;
}
K. 小 C 的神秘图形
看了眼就扔给祁神了,然后被祁神秒了
#include<bits/stdc++.h>
using namespace std;signed main() {ios::sync_with_stdio(0); cin.tie(0);int n; cin >> n;string str1, str2;cin >> str1 >> str2;bool ok=true;for (int i=0; i<n; ++i) {if (str1[i]!='1' && str2[i]!='1') {ok=false; break;}}cout << (ok ? "1\n" : "0\n");return 0;
}
Postscript
这场感觉题太偏向于计数了,导致后期基本没啥事情干,怪不得 downvote 这么多
据说 BIT 也很喜欢出计数,希望昆明能来点适合牢闪的题的说