2020ICPC江西省大学生程序设计竞赛

比赛链接

2020ICPC 江西省大学生程序设计竞赛

A.Simple Math Problem

求解 i=1nj=1i[gcd(i,j)==1]f(j),其中 f(j)j 的数位和

解题思路

暴力,容斥原理

先预处理所有的 f(j),对于每个 f(j),找出 jinij 互质的个数 cntcnt×f(j) 即为 j 的贡献,关键在于计算 cnt,根据前缀和,即找 1inij 互质的个数,由于 nj,直接计算与 j 互质的个数不好做,相反计算与 j 不互质的个数比较方便,即先对 j 分解质因数种类:p1,p2,,pk,然后找 1n 内倍数为 p1,,pk 的数个数,其中也得用到容斥原理

设平均质因子种数为 C,则:

  • 时间复杂度:(n×C×2C)

莫比乌斯函数

i=1nj=1if(j)[(i,j)=1] 等价于 i=1nj=inf(i)[(i,j)=1],由容斥原理,i=1nj=inf(i)[(i,j)=1]i=1nj=1nf(i)[(i,j)=1]i=1nj=1if(i)[(i,j)=1]+f(1),而 [(i,j)=1]=d(i,j)μ(d),则 i=1nj=1nf(i)d(i,j)μ(d)i=1nf(i)ϕ(i)+f(1),将 d 提到外面,d 的范围为 [1,n],此时需满足 d(i,j),设 i=i/d,j=j/d,变换,d=1nμ(d)ndi=1ndf(id)i=1nf(i)ϕ(i)+f(1)

求解后面一部分,直接线性筛求解欧拉函数即可,前面一部分计算 i=1ndf(id)O() 复杂度求解,故:

  • 时间复杂度:O(nlogn)

代码

  • 莫比乌斯函数
// Problem: A Simple Math Problem // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/36726/A // Memory Limit: 262144 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=1e5+5; int n,prime[N],m,v[N],phi[N],u[N],f[N],g[N]; void init(int n) { u[1]=phi[1]=f[1]=1; for(int i=2;i<=n;i++) { f[i]=f[i/10]+i%10; if(v[i]==0) { prime[++m]=v[i]=i; u[i]=-1; phi[i]=i-1; } for(int j=1;j<=m&&i*prime[j]<=n;j++) { if(v[i]<prime[j])break; if(i%prime[j]==0)u[i*prime[j]]=0; else u[i*prime[j]]=-u[i]; v[i*prime[j]]=prime[j]; phi[i*prime[j]]=phi[i]*(i%prime[j]?prime[j]-1:prime[j]); } } for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=i)g[i]+=f[j]; } int main() { cin>>n; init(n); LL res=0; for(int i=1;i<=n;i++) res+=1ll*u[i]*(n/i)*g[i]-f[i]*phi[i]; res+=f[1]; cout<<res; return 0; }
  • 暴力
// Problem: A Simple Math Problem // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/36726/A // Memory Limit: 262144 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=1e5+5; int f(int x) { int res=0; while(x)res+=x%10,x/=10; return res; } int F[N],n,prime[N],m,v[N]; void init(int n) { for(int i=2;i<=n;i++) { if(v[i]==0) v[i]=prime[++m]=i; for(int j=1;j<=m&&i*prime[j]<=n;j++) { if(v[i]<prime[j])break; v[i*prime[j]]=prime[j]; } } for(int i=1;i<=n;i++)F[i]=f(i); } vector<int> Div(int x) { vector<int> res; unordered_set<int> S; while(v[x]) { if(!S.count(v[x]))S.insert(v[x]),res.pb(v[x]); x/=v[x]; } return res; } int cal(int x,int n) { vector<int> a=Div(x); int res=0; if(x>n) { res=x; for(int i:a)res/=i,res*=(i-1); if(x==1)res--; return res; } res=n; int sz=a.size(); for(int i=0;i<(1<<sz);i++) { int t=1,sign=1; for(int j=0;j<sz;j++) if(i>>j&1)t*=a[j],sign*=-1; if(t!=1)//注意 res+=sign*n/t; } return res; } int main() { init(N-1); cin>>n; LL res=0; for(int i=1;i<=n;i++)res+=1ll*F[i]*(cal(i,n)-cal(i,i-1)); cout<<res; return 0; }

C.Charging

数轴上有 [1,n] 一共 n 个点, m 个区间分别是 [li,ri] ,设 tot 为所选取的区间数量, x 为所有所选取的区间的交集长度, 求 min( tot,x) 的最大值

解题思路

树状数组,二分

所选取的交集区间一定是某个区间的左端点,某个区间的右端点,不妨枚举所选取区间的右端点 r,即按将所有区间按右端点从大到小排序,枚举右端点时前面的点的右端点都满足右端点条件,设所选取区间的左端点为 l,则利用树状数组可计算前面的区间左端点不大于 l 的点即满足包含区间 [l,r] 的区间的个数,即选取的区间的个数 f=(l)=tot,此时交集长度为 rl+1,而要求 min(rl+1,f(l)) 最大,其中 r 固定,rl+1 为减函数,f(l) 为增函数,当两者最接近时最优,故可二分 l,使其满足 rl+1f(l) 的最小的 l,答案在 ll1

  • 时间复杂度:O(nlognlogn)

代码

// Problem: Charging // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/36726/C // Memory Limit: 262144 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> // #define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=3e5+5; int n,m,res,tr[N]; struct A { int l,r; bool operator<(const A &o) { if(r==o.r)return l<o.l; return r>o.r; } }a[N]; int ask(int x) { int res=0; for(;x;x-=x&-x)res+=tr[x]; return res; } void add(int x,int y) { for(;x<N;x+=x&-x)tr[x]+=y; } int main() { read(n),read(m); for(int i=1;i<=m;i++)read(a[i].l),read(a[i].r); sort(a+1,a+1+m); for(int i=1;i<=m;i++) { int l=1,r=a[i].r; add(a[i].l,1); while(l<r) { int mid=l+r>>1; if(a[i].r-mid+1<=ask(mid))r=mid; else l=mid+1; } res=max(res,min(ask(l),a[i].r-l+1)); l--; res=max(res,min(ask(l),a[i].r-l+1)); } cout<<res; return 0; }

D.Chinese Valentine's Day

给出 n 个整数字符串,要求所有本质不同的子串的和

解题思路

后缀自动机

本质上是广义后缀自动机,但可转化为普通后缀自动机,即在 n 个字符串之间插入一个分割字符,将 n 个字符串合并为一个字符串,构建 SAM,由于 SAM 的有向无环图中的所有路径与所有子串一一对应,设某一个状态节点 u 的贡献为 res[u],每次按某一个字符 i 扩展到状态节点 v,贡献转移即 res[v]+=10×res[u]+i×sz[u],其中 sz[i] 表示状态节点 i 表示的子串数量即 |endpos[i]|,每次扩展需要前一个状态转移过来,拓扑排序计算贡献即可,同时计算贡献时只需在非分割字符扩展时计算

  • 时间复杂度:O(i=1n|s[i]|)

代码

// Problem: Chinese Valentine's Day // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/36726/D // Memory Limit: 524288 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int mod=1e9+7,N=3e6+5; int n,lst=1,cnt=1,sz[N],res[N],in[N]; char s[N]; struct Node { int len,fa; int ch[11]; }node[N]; void extend(int c) { int p=lst,np=lst=++cnt; node[np].len=node[p].len+1; for(;p&&!node[p].ch[c];p=node[p].fa)node[p].ch[c]=np; if(!p)node[np].fa=1; else { int q=node[p].ch[c]; if(node[q].len==node[p].len+1)node[np].fa=q; else { int nq=++cnt; node[nq]=node[q]; node[nq].len=node[p].len+1; node[q].fa=node[np].fa=nq; for(;p&&node[p].ch[c]==q;p=node[p].fa)node[p].ch[c]=nq; } } } void topSort() { queue<int> q; for(int i=1;i<=cnt;i++) { for(int j=0;j<=10;j++) { int to=node[i].ch[j]; if(to)in[to]++; } } for(int i=1;i<=cnt;i++) if(!in[i]) { sz[i]=1; q.push(i); } while(q.size()) { int x=q.front(); q.pop(); for(int i=0;i<=10;i++) { int y=node[x].ch[i]; if(!y)continue; if(i!=10) { res[y]=(res[y]+1ll*res[x]*10+i*sz[x])%mod; sz[y]=(sz[y]+sz[x])%mod; } if(--in[y]==0)q.push(y); } } } int main() { scanf("%d",&n); while(n--) { scanf("%s",s); for(int i=0;s[i];i++)extend(s[i]-'0'); ; extend(10); } topSort(); LL ret=0; for(int i=2;i<=cnt;i++) ret=(ret+res[i])%mod; cout<<ret; return 0; }

E.Color Sequence

给定一个颜色序列,求它有多少个颜色出现次数都是偶数的连续子序列

解题思路

思维

颜色数量比较少,可以考虑用二进制来表示每一种颜色,要求每种颜色出现次数为偶数,即表示的连续子序列异或值为 0,不妨先求出前缀异或值 s,考虑每个数的贡献,对于当前数下标 i,求前面有多少个数 j,满足 s[i]s[j1]=0,直接统计前面 s[i] 的个数即可

  • 时间复杂度:O(n)

代码

// Problem: Color Sequence // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/36726/E // Memory Limit: 131072 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> // #define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=1e6+5; int n,s[N],cnt[1<<21]; LL res; int main() { cin>>n; for(int i=1;i<=n;i++) { int x; cin>>x; s[i]=s[i-1]^(1<<x); } cnt[0]=1; for(int i=1;i<=n;i++)res+=cnt[s[i]],cnt[s[i]]++; cout<<res; return 0; }

F.Magical Number

定义魔数:其前缀表示的数都能整除该数的位数,每一个个位数都由下列火柴棒组成:
image
n1n10100) 个火柴棒恰能组成的最大的数

解题思路

暴力dfs

对于 i 位的魔数 x,后面组成的数 y=x+[0,9],共有 10 条分支,要求 y 整除 i+1,这样的条件可想而知,分支数会越来越少,不妨考虑暴力 dfs,用 int 速度控制在了 1 秒内,不妨直接用 __int128,会发现最大值为 3608528850368400786036725,没有超出 __int128 的范围,所以位数大概到了 24 的量级就没有出现分支了

共有 24 层,每层直接最多有 10 个分支,但实际上很少,故:

  • 时间复杂度:O()

代码

// Problem: Magical Number // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/36726/F // Memory Limit: 262144 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } template <typename T> void print(T x) { if (x < 0) putchar('-'), x = -x; if (x < 10) putchar(x + 48); else print(x / 10), putchar(x % 10 + 48); } #define int __int128 string s; int n,res=-1,a[]={6,2,5,5,4,5,6,3,7,6}; void dfs(int len,int x,int cost) { if(cost<=0) { if(cost==0)res=max(res,x); return ; } for(int i=0;i<=9;i++) { int y=x*10+i; if(y%(len+1)==0)dfs(len+1,y,cost-a[i]); } } signed main() { cin>>s; if(s.size()<=3) { n=atoi(s.c_str()); for(int i=1;i<=9;i++) dfs(1,i,n-a[i]); } print(res); return 0; }

G.Mathematical Practice

n 个数,要求 m 个子集,使得子集之间没有交集,求方案数

解题思路

放球问题

由于 n,m 比较大,考虑结论
问题转化:将 n 个不同的球放入 m 个不同盒子(不一定非要放入盒子中)的方案数,即经典放球问题,每个球有 m+1 种选择,答案即为 (m+1)n

  • 时间复杂度:O(logn)

代码

// Problem: Mathematical Practice // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/36726/G // Memory Limit: 262144 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int mod=998244353; int n,m; int ksm(int a,int b,int p) { int res=1%p; while(b) { if(b&1)res=1ll*res*a%p; a=1ll*a*a%p; b>>=1; } return res; } int main() { cin>>n>>m; cout<<ksm(m+1,n,mod); return 0; }

J.Split Game

一张 n×m 的纸,两个人轮流操作,可以沿着一条直线切,当有人切出 1×1 的纸时输掉比赛,问最后谁赢

解题思路

博弈论,sg函数

219. 剪纸游戏 不同的是,这里要求切出 1×1 的纸时输掉比赛,则 1×1 为终止态,在进行剪纸时不能剪到 1×1 的状态再计算 mex 即可

  • 时间复杂度:O(n3)

代码

// Problem: Split Game // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/36726/J // Memory Limit: 262144 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=155; int f[N][N],n,m; int sg(int n,int m) { if(f[n][m]!=-1)return f[n][m]; unordered_set<int> s; for(int i=1;i<=n-i;i++) if(m!=1||i!=1&&n-i!=1)s.insert(sg(i,m)^sg(n-i,m)); for(int i=1;i<=m-i;i++) if(n!=1||i!=1&&m-i!=1)s.insert(sg(n,i)^sg(n,m-i)); for(int i=0;;i++) if(!s.count(i))return f[n][m]=f[m][n]=i; } int main() { memset(f,-1,sizeof f); f[1][1]=0; while(cin>>n>>m)puts(sg(n,m)?"Alice":"Bob"); return 0; }

L.WZB's Harem

给你一张 n×n(n20) 的图,图上有 01 两种数字,现在有 n 个互不相同的皇后,需要将这 n 个皇后放在 0 的位置上,并且保证这 n 个皇后之间不存在任意两个皇后处于同一行、同一列,问方案数。

解题思路

状压dp

  • 状态表示:f[i][j] 表示前 i 行,状态为 j 的方案数,j 表示前 i 个皇后选在哪些列的状态
  • 状态计算:f[i][j|1<<k]+=f[i1][j]
    分析:j 二进制下含有 i11,且第 k 位为 0,则在 a[i][j]=0 的前提下可转移到第 i

O(n2) 枚举行和列,再枚举含有对应 1 的状态,最坏情况下有 Cnn2 种状态,则:

  • 时间复杂度:O(n2×Cnn2)

代码

// Problem: WZB's Harem // Contest: NowCoder // URL: https://ac.nowcoder.com/acm/contest/36726/L // Memory Limit: 262144 MB // Time Limit: 2000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=20,mod=1e9+7; int f[N][1<<N],n,a[N][N]; vector<int> b[N]; int main() { cin>>n; for(int i=0;i<n;i++) for(int j=0;j<n;j++)cin>>a[i][j]; for(int i=0;i<(1<<n);i++)b[__builtin_popcount(i)].pb(i); for(int i=0;i<n;i++) for(int j=0;j<n;j++) { if(a[i][j])continue; for(int k=0;k<b[i].size();k++) { if((b[i][k]>>j&1)==0) { if(i) f[i][b[i][k]|1<<j]=(f[i][b[i][k]|1<<j]+f[i-1][b[i][k]])%mod; else f[i][b[i][k]|1<<j]++; } } } int res=f[n-1][(1<<n)-1]; for(int i=1;i<=n;i++)res=1ll*res*i%mod; cout<<res; return 0; }

__EOF__

本文作者acwing_zyy
本文链接https://www.cnblogs.com/zyyun/p/16417693.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zyy2001  阅读(213)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示