The 2021 CCPC Weihai Onsite
Preface
又被打爆了,看了下榜这场罚时比较炸喜提银首咯
不过yysy这场题出的还是挺好的,medium题都挺有意思需要想一想
但就是感觉考的组合计数这一块有点太多了,而且因为有人歪榜开局过了M,导致我前期一直在这道题上坐牢,最后还是徐神出马一套生成函数秒了此题
A. Goodbye, Ziyin!
签到题,如果有度数\(>3\)的点就一定不合法,否则所有度数\(<3\)的点都可以为根
然而这题直接scanf
会TLE,不得不去copy个读优
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
#define Tp template <typename T>
using namespace std;
const int N=1e6+5;
int n,x,y,deg[N];
class FileInputOutput
{
private:
static const int S=1<<21;
#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
#define pc(ch) (Ftop!=Fend?*Ftop++=ch:(fwrite(Fout,1,S,stdout),*(Ftop=Fout)++=ch))
char Fin[S],Fout[S],*A,*B,*Ftop,*Fend; int pt[30];
public:
inline FileInputOutput(void) { Ftop=Fout; Fend=Fout+S; }
Tp inline void read(T& x)
{
x=0; char ch; while (!isdigit(ch=tc()));
while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
}
Tp inline void write(T x)
{
RI ptop=0; while (pt[++ptop]=x%10,x/=10);
while (ptop) pc(pt[ptop--]+48); pc('\n');
}
inline void flush(void)
{
fwrite(Fout,1,Ftop-Fout,stdout);
}
#undef tc
#undef pc
}F;
int main()
{
//freopen("A.in","r",stdin); freopen("A.out","w",stdout);
RI i; for (F.read(n),i=1;i<n;++i)
F.read(x),F.read(y),++deg[x],++deg[y];
int ans=0; for (i=1;i<=n;++i)
{
if (deg[i]>3) { ans=0; break; }
if (deg[i]<3) ++ans;
}
return printf("%d",ans),0;
}
B. Subset
妈的怎么这么多数学题
C. Assign or Multiply
看到这题秒出了一个原根转换后再bitset
做法,但感觉这数据范围铁跑不过就没写了
D. Period
比较Easy的字符串,被徐神一眼秒了,我题目都没看过
#include <bits/stdc++.h>
char s[1000005], c, *p;
int fail[1000005];
inline int readi() {
int res = 0;
while(c > '9' || c < '0') c = getchar();
while(c >= '0' && c <= '9')
res = res * 10 + (c ^ 48),
c = getchar();
return res;
}
int main() {
// freopen("1.in", "r", stdin);
for(c = getchar(), p = s + 1; c != '\n' && c != '\r'; c = getchar()) *p++ = c;
int n = p - s - 1, q;
fail[0] = fail[1] = 0;
for(int i = 2, j = 0; i <= n; ++i) {
while(j && s[j + 1] != s[i]) j = fail[j];
if(s[j + 1] == s[i]) j += 1;
fail[i] = j;
}
std::vector<int> prd{0};
for(int i = n; i; i = fail[i]) prd.push_back(i);
std::sort(prd.begin(), prd.end());
// for(int i = 0; i < prd.size(); ++i) std::cout << prd[i] << char(i == prd.size() - 1 ? 10 : 32);
q = readi();
while(q--) {
int p = readi();
int s = std::min(p - 1, n - p);
auto l = prd.begin(), r = prd.end();
while(l < r) {
auto mid = l + (r - l + 1) / 2;
if(*mid > s) r = mid - 1;
else l = mid;
}
std::cout << l - prd.begin() << char(10);
}
return 0;
}
E. CHASE!
ORZ祁神秒了此题
考虑设\(E(i)\)表示还能重选\(0\)次时的期望得分,则初始时\(E(0)=\frac{\sum_{i=1}^n (n-1)\times a_i}{C_n^2}\)
考虑对于\(E(1)\)来说,如果选出的数对\(a_i+a_j\ge E(0)\)的话就不需要重选,否则可以考虑重选,类似的我们可以把\(E(i-1)\)作为求\(E(1)\)的基准
由于\(k\)很小所以可以暴力\(O(nk)\)DP处理,每次转移求数对的个数可以用two pointers
求解
而有了这个思路后询问也很简单了,注意特判\(c=0\)的情形
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#define int long long
#define double long double
#define RI register int
#define CI const int&
using namespace std;
const double EPS=1e-4;
const int N=100005;
int n,k,m,ta[N],a[N],pfx[N],all,x,y,c; double E[105];
inline int dcmp(const double& x)
{
if (fabs(x)<EPS) return 0; return x<0?-1:1;
}
signed main()
{
//freopen("E.in","r",stdin); freopen("E.out","w",stdout);
RI i,p,q; for (scanf("%lld%lld%lld",&n,&k,&m),i=1;i<=n;++i) scanf("%lld",&a[i]),ta[i]=a[i];
for (all=n*(n-1)/2LL,i=1;i<=n;++i) E[0]+=1.0L*(n-1)*a[i]; E[0]/=all;
for (sort(a+1,a+n+1,greater<int>()),i=1;i<=n;++i) pfx[i]=pfx[i-1]+a[i];
for (i=1;i<=k;++i)
{
int grt=0,sum=0; for (p=1,q=n;p<q;++p)
{
while (p<q&&a[p]+a[q]<E[i-1]) --q;
if (p<q) grt+=q-p,sum+=(q-p)*a[p]+pfx[q]-pfx[p];
}
E[i]=(1.0L*(all-grt)*E[i-1]+sum)/all;
}
for (printf("%.10Lf\n",E[k]),i=1;i<=m;++i)
{
scanf("%lld%lld%lld",&x,&y,&c);
if (c==0) { puts("accept"); continue; }
int d=dcmp(ta[x]+ta[y]-E[c-1]);
if (d<0) puts("reselect"); else
if (d>0) puts("accept"); else puts("both");
}
return 0;
}
F. Stone
我上机写E的一点功夫这题就被祁神秒了,我直接ORZ
考虑如果对于当前的局面,存在某堆石子为奇数,那么先手只要在第一步把所有堆都拿成偶数,接下来就可以通过镜像操作获胜了
否则考虑所有堆都是偶数的情况,则所有人都不能取奇数,那么就等价于把所有数都除以\(2\)然后再判断上述情况即可
最后的答案就是\(\frac{1+\min a_i}{2}\),总复杂度\(O(n\times \log a_i)\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6+5;
int n, A[N];
signed main(){
scanf("%lld", &n);
for (int i=1; i<=n; ++i) scanf("%lld", &A[i]);
bool ok=false;
do{
for (int i=1; i<=n; ++i){
if (A[i]%2){
ok=true;
break;
}
}
if (!ok) for (int i=1; i<=n; ++i) A[i]/=2;
}while (!ok);
int ma=1e9+9;
for (int i=1; i<=n; ++i) if (A[i]%2) ma=min(ma, A[i]);
printf("%lld\n", (ma+1)/2);
return 0;
}
G. Shinyruo and KFC
首先不难发现对于固定的\(k\),答案其实就是\(\prod_{i=1}^n C_{k}^{a_i}\)
然后在想了各种奇奇怪怪的多项式科技后,终于顿悟了\(\sum_{i=1}^n a_i\le 10^5\)的限制的作用,就是保证最后不同的\(a_i\)只有\(\sqrt {10^5}\)种,因此可以直接暴力求解
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,mod=998244353;
int n,m,a[N],fact[N],ifac[N],c[N],cnt;
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;
}
inline void init(CI n)
{
RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
for (ifac[n]=quick_pow(fact[n]),i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
if (n<0||m<0||n<m) return 0;
return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
int main()
{
//freopen("G.in","r",stdin); freopen("G.out","w",stdout);
RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%d",&a[i]),++c[a[i]];
for (i=0;i<=100000;++i) if (c[i]) a[++cnt]=i;
for (init(100000),i=1;i<=m;++i)
{
int ret=1; for (j=1;j<=cnt;++j)
ret=1LL*ret*quick_pow(C(i,a[j]),c[a[j]])%mod;
printf("%d\n",ret);
}
return 0;
}
H. city safety
怎么大家都会树上DP的做法,就我苦思冥想了半天才会网络流建图……
这里简单讲一下我的最小割做法吧,感觉和题解的好像还不太一样
首先假设每个点都取得最大答案\(v_{n-1}\),考虑减去尽量少的值来得到一种合法的方案
- 对于原图中的\(i\in [1,n]\)点,连\(i\to t\),容量为\(w_i\)的边,割掉这条边表示将这个点选上
- 新建\(n\times n\)个辅助点,第\(i\in[1,n]\)行,第\(j\in[0,n-1]\)列的点表示将与原图中与\(i\)距离等于\(j\)的所有点保留
- 将\(s\)与每行的第一个点连边,容量为\(v_0\)
- 将每行的第\(j\)个点与第\(j+1\)个点连边,容量为\(v_{n-1-v_j}\)
- 将\((i,j)\)这个辅助点向原图中满足\(dis(i,k)=j\)的点\(k\)连边,边权为\(\infty\)
然后跑一个最小割用\(n\times v_{n-1}\)减去即可,具体合理性可以把图画出来手玩一下
这个做法其实并没有用到树的性质,而这题还有一个我看不太懂原理但很好写的树上DP做法,纯被吊打
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=205,INF=1e12;
int n,w[N],v[N],dis[N][N],x,y,id[N][N],ID[N],idx;
namespace Network_Flow
{
const int NN=100005,MM=1e7+5;
struct edge
{
int to,nxt,v;
}e[MM<<1]; int cnt=1,head[NN],cur[NN],dep[NN],s,t;
inline void addedge(CI x,CI y,CI z)
{
//printf("%lld -> %lld (%lld)\n",x,y,z);
e[++cnt]=(edge){y,head[x],z}; head[x]=cnt;
e[++cnt]=(edge){x,head[y],0}; head[y]=cnt;
}
#define to e[i].to
inline bool BFS(void)
{
memset(dep,0,(t+1)*sizeof(int)); dep[s]=1;
queue <int> q; q.push(s);
while (!q.empty())
{
int now=q.front(); q.pop();
for (RI i=head[now];i;i=e[i].nxt)
if (e[i].v&&!dep[to]) dep[to]=dep[now]+1,q.push(to);
}
return dep[t];
}
inline int DFS(CI now,CI tar,int dis)
{
if (now==tar) return dis; int ret=0;
for (RI& i=cur[now];i&&dis;i=e[i].nxt)
if (e[i].v&&dep[to]==dep[now]+1)
{
int dist=DFS(to,tar,min(dis,e[i].v));
if (!dist) dep[to]=INF;
dis-=dist; ret+=dist; e[i].v-=dist; e[i^1].v+=dist;
if (!dis) return ret;
}
if (!ret) dep[now]=0; return ret;
}
#undef to
inline int Dinic(int ret=0)
{
while (BFS()) memcpy(cur,head,(t+1)*sizeof(int)),ret+=DFS(s,t,INF); return ret;
}
};
using namespace Network_Flow;
signed main()
{
// freopen("H.in","r",stdin); freopen("H.out","w",stdout);
RI i,j,k; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&w[i]);
for (i=0;i<n;++i) scanf("%lld",&v[i]);
for (i=1;i<=n;++i) for (j=1;j<=n;++j) dis[i][j]=i!=j?INF:0;
for (i=1;i<n;++i) scanf("%lld%lld",&x,&y),dis[x][y]=dis[y][x]=1;
for (k=1;k<=n;++k) for (i=1;i<=n;++i) for (j=1;j<=n;++j)
dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
for (s=idx=1,i=1;i<=n;++i) for (j=0;j<n;++j) id[i][j]=++idx;
for (i=1;i<=n;++i) ID[i]=++idx; t=++idx;
for (i=1;i<=n;++i)
{
addedge(s,id[i][0],v[n-1]); addedge(ID[i],t,w[i]);
for (j=0;j+1<n;++j) addedge(id[i][j],id[i][j+1],v[n-1]-v[j]);
for (j=0;j<n;++j) for (k=1;k<=n;++k)
if (dis[i][k]==j) addedge(id[i][j],ID[k],INF);
}
return printf("%lld",n*v[n-1]-Dinic()),0;
}
I. Distance
这题好像之前暑假前集训做过一个类似的来着?不过好像本质也不太一样,弃了弃了
(妈的怎么都是数学题)
J. Circular Billiard Table
签到题,手玩一下会发现答案就是\(\frac{\operatorname{LCM(180b,a)}}{a}-1\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
int t,a,b;
inline __int128 LCM(const __int128& x,const __int128& y)
{
return x/__gcd(x,y)*y;
}
inline void print(const __int128& x)
{
if (x>9) print(x/10); putchar(x%10+'0');
}
int main()
{
//freopen("J.in","r",stdin); freopen("J.out","w",stdout);
for (scanf("%d",&t);t;--t)
scanf("%d%d",&a,&b),print(LCM(180LL*b,a)/a-1),putchar('\n');
return 0;
}
K. Tiny Stars
神之构造题,不得不佩服出题人的脑洞
考虑要让连通块尽量小,而由于又是一个外向图很容易想到把每个连通块搞成一个环
而注意到\(i\to \frac{i}{a}\to \frac{a^2}{i}\to \frac{a}{i}\to i\)(分别执行\(0,1,0,1\)操作),因此只要能实现交替连边,就可以把图变成若干个四元环,代价就是\(4n\)
而这样连边时会均摊\(0/1\)的个数,因此连边的复杂度就是\(\frac{n}{2}\times (1+\lceil\log_2 n\rceil)\le 7.5n\),加上前面的\(4n\)恰好小于\(12n\)的限制
现在的问题就是怎么实现这个交替连边的过程了,要求使得每个\(i\)连向的点的状态都和自己的不同
一种很妙的方法是先给\(n\)求个原根\(g\),把乘除法转换为指数上的加减法,然后\(i\)的状态为\(0\)当且仅当\(i=g^x\)且\(x\)为奇数
不难发现此时只要\(a\)的指数为奇数,那么这个过程就是对的,因此出错概率为\((\frac{1}{2})^{20}\),完全可以忽略
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=10005;
int n,g,ans[N];
inline int quick_pow(int x,int p,int mod,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline vector <int> resolve(int x)
{
vector <int> frac;
for (RI i=2;i*i<=x;++i) if (x%i==0)
{
frac.push_back(i);
while (x%i==0) x/=i;
}
if (x>1) frac.push_back(x);
return frac;
}
inline int get_root(CI p)
{
vector <int> frac=resolve(p-1);
for (RI i=2;i<p;++i)
{
bool flag=1; for (auto x:frac)
if (quick_pow(i,(p-1)/x,p)==1) { flag=0; break; }
if (flag) return i;
}
}
int main()
{
//freopen("K.in","r",stdin); freopen("K.out","w",stdout);
RI i; for (scanf("%d",&n),i=1;i<=n;++i) ans[i]=1;
for (g=get_root(n),i=1;i<n;i+=2) ans[quick_pow(g,i,n)]=0;
for (i=1;i<n;++i) putchar(ans[i]+'0'),putchar(' ');
return 0;
}
L. shake hands
题目都没看,经典防AK题
M. 810975
810975,新人怎么听不懂,没想到短短两年后炉石国服就寄了,默哀
这题我们队推不出容斥做法,因此只能靠徐神通过生成函数优化DP的式子,推出来最长段的长度小于等于\(k\)的方案数就是
然后上去大力Rush了个多项式快速幂板子切了这道题,赛后一看妈的大家都会容斥
不过后面看了下参考的经典容斥问题是我18年打过的杭电多校来着,我直接呃呃
#include <bits/stdc++.h>
using llsi = long long signed int;
const int $n = 1 << 19;
const llsi mod = 998244353, G = 3, Gi = 332748118;
llsi ksm(llsi a, llsi b) {
llsi res = 1;
while(b) {
if(b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
int tae[$n] {0};
void otae(int n) {
for(int i = 1; i < n; ++i)
tae[i] = tae[i >> 1] >> 1 | ((i & 1) ? n >> 1 : 0);
return ;
}
int c2(int n) {
int i = 1; while(i < n) i <<= 1;
return i;
}
void NTT(llsi *a, int an, int on) {
for(int i = 1; i < an; ++i) if(tae[i] > i) {
llsi tmp = a[i]; a[i] = a[tae[i]];
a[tae[i]] = tmp;
}
for(int i = 1; i < an; i <<= 1) {
const llsi wi = ksm(on > 0 ? G : Gi, (mod - 1) / (i << 1));
for(int j = 0; j < an; j += i << 1) {
llsi w = 1;
for(int k = 0; k < i; ++k) {
llsi x = a[j + k], y = a[j + k + i] * w % mod;
a[j + k] = (x + y) % mod;
a[j + k + i] = (x + mod - y) % mod;
w = w * wi % mod;
}
}
}
if(on < 0) {
llsi inv = ksm(an, mod - 2);
for(int i = 0; i < an; ++i) a[i] = a[i] * inv % mod;
}
return ;
}
llsi a[$n], b[$n], c[$n], d[$n];
llsi n, m, c2m;
llsi solve(llsi k) {
if(k == 0) return !m;
c2m = c2(m << 1);
otae(c2m);
for(int i = 0; i <= k; ++i) a[i] = 1;
memset(a + k + 1, 0, sizeof(llsi) * (c2m - k - 1));
b[0] = 1;
memset(b + 1, 0, sizeof(llsi) * (c2m - 1));
int ss = n - m + 1;
while(ss) {
NTT(a, c2m, 1);
if(ss & 1) {
NTT(b, c2m, 1);
for(int i = 0; i < c2m; ++i) b[i] = (b[i] * a[i]) % mod;
NTT(b, c2m, -1);
memset(b + m + 1, 0, sizeof(llsi) * (c2m - m - 1));
}
for(int i = 0; i < c2m; ++i) a[i] = (a[i] * a[i]) % mod;
NTT(a, c2m, -1);
memset(a + m + 1, 0, sizeof(llsi) * (c2m - m - 1));
ss >>= 1;
}
return b[m];
}
int main() {
// llsi a[] = {2, 1, 0, 0};
// otae(4);
// NTT(a, 4, 1);
// for(int i = 0; i < 4; ++i) a[i] = a[i] * a[i] % mod;
// NTT(a, 4, -1);
// for(int i = 0; i < 4; ++i) std::cout << a[i] << char(i == 3 ? 10 : 32);
// return 0;
llsi k;
std::cin >> n >> m >> k;
if(n < m || m < k) std::cout << "0\n";
else std::cout << (solve(k) - solve(k - 1) + mod) % mod << char(10);
return 0;
}
Postscript
珍爱生命,远离数数题