2019中国大学生程序设计竞赛(CCPC) - 网络选拔赛
(7题弟弟。C题知道正解,懒得写了)
A:^&^ HDU - 6702
题意:给出A,B。求一个最小的C,使得min=(A^C)&(B^C)最小。
思路:如果存在A和B都有的位,那么全选,就行了,这时结果min为0; 否则,选最小的那个,一个有,一个没有的那一位p,结果min=1<<p;
#include<bits/stdc++.h> #define ll long long using namespace std; int T; unsigned int A,B,C; template<class T> inline void read(T&a){ char c=getchar(); for(a=0;(c<'0'||c>'9')&&c!='-';c=getchar()); bool f=0;if(c=='-')f=1,c=getchar(); for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0'; if(f)a=-a; } int main(){ for(read(T);T--;){ read(A),read(B);C=0; for(long long i=1;i<=A&&i<=B;i<<=1) if((i&A)&&(i&B))C|=i; if(!C){ for(long long i=1;i<=A||i<=B;i<<=1) if((i&A)||(i&B)){C|=i;break;} } printf("%u\n",C); } return 0; }
B:array HDU - 6703
题意:有两种操作,第一种单点修改。 第二种,给出(R,K),查询大于等于K的最小数,满足在a[1,R]中没有出现。 强制在线。
思路:记录每个数出现的最小位置,把它们插入线段树。那么就是在[K,N]中找第一个对应值>R的,所以线段树保存的信息的区间最大值。 这个由于线段树自带二分功能,即如果作区间有Mx>R的,那么在左区间找答案,是否在右区间找; set保存相同数的最小位置; 所以整体的复杂度就是O(NlogN);
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=400010; int a[maxn],Mx[maxn],RR; set<int>s[maxn]; set<int>::iterator it; void read(int &x){ x=0; char c=getchar(); while(c>'9'||c<'0') c=getchar(); while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); } void update(int Now,int L,int R,int pos,int val) { if(L==R){ Mx[Now]=val; return ; } int Mid=(L+R)>>1; if(pos<=Mid) update(Now<<1,L,Mid,pos,val); else update(Now<<1|1,Mid+1,R,pos,val); Mx[Now]=max(Mx[Now<<1],Mx[Now<<1|1]); } int query(int Now,int L,int R,int l,int r,int RR) { int Mid=(L+R)>>1,res=0; if(Mx[Now]<=RR) return 0; if(l<=L&&r>=R) { if(L==R&&Mx[Now]>RR) return L; if(Mx[Now<<1]>RR) return query(Now<<1,L,Mid,l,r,RR); if(Mx[Now<<1|1]>RR) return query(Now<<1|1,Mid+1,R,l,r,RR); return 0; } if(l<=Mid) res=max(res,query(Now<<1,L,Mid,l,r,RR)); if(res) return res; if(r>Mid) res=max(res,query(Now<<1|1,Mid+1,R,l,r,RR)); return res; } int main() { int T,N,M,ans,opt,K,pos; scanf("%d",&T); while(T--){ ans=0; scanf("%d%d",&N,&M); rep(i,1,N) s[i].clear(); rep(i,1,N<<2) Mx[i]=0; rep(i,1,N) read(a[i]); rep(i,1,N) s[a[i]].insert(i); rep(i,1,N){ if(s[i].empty()) continue; int t=*s[i].begin(); update(1,1,N,i,t); } rep(i,1,M){ read(opt); if(opt==1){ read(pos); pos^=ans; if(a[pos]<=N) { it=s[a[pos]].lower_bound(pos); if(it==s[a[pos]].begin()){ s[a[pos]].erase(it); int tmp=N+1; if(!s[a[pos]].empty()) tmp=*s[a[pos]].begin(); if(tmp==N+1) update(1,1,N,a[pos],N+1); else update(1,1,N,a[pos],tmp); } else s[a[pos]].erase(it); a[pos]+=10000000; } } else { read(RR); read(K); RR^=ans; K^=ans; ans=query(1,1,N,K,N,RR); if(ans==0) ans=N+1; printf("%d\n",ans); } } } return 0; }
C:K-th occurrenceHDU - 6704
题意:给定字符串S,Q次询问,每次给出(L,R,K),让你求S中 str[L,R]这个子串第K次出现的位置。
思路:显然是SA的区间问题,可以RMQ出一个区间,满足这个区间的min(ht)>=R-L+1;然后在这个区间主席树求第K大。
代码,稍等两天。
update-8-30 : 作为一个sam的真粉,最终还是用sam把这题补了。
前置知识点:1,线段树的启发式合并,维护每个节点的endpos。 2,倍增可以快速地转移到某个子串在sam中的位置。
3,线段树可以二分第K个数。
补了两个CF的题,可以对照一下:
CodeForces - 666E: Forensic Examination (SAM 线段树合并)
CodeForces - 1037H: Security(SAM+线段树合并)
#include<bits/stdc++.h> #define rep2(i,a,b) for(int i=a;i>=b;i--) #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=200010; char c[maxn]; int rt[maxn],pos[maxn],N,Q,tot,f[maxn][20]; struct in{ int L,R,sum; }s[maxn*90]; void ins(int &now,int L,int R,int p) { if(!now) now=++tot; s[now].sum++; if(L==R) return ; int Mid=(L+R)>>1; if(p<=Mid) ins(s[now].L,L,Mid,p); else ins(s[now].R,Mid+1,R,p); } void merge(int &now,int x,int y,int L,int R) { if(!x||!y) { now= x|y; return ;} now=++tot; if(L==R) { s[now].sum=s[x].sum+s[y].sum; return ; } int Mid=(L+R)>>1; merge(s[now].L,s[x].L,s[y].L,L,Mid); merge(s[now].R,s[x].R,s[y].R,Mid+1,R); s[now].sum=s[s[now].L].sum+s[s[now].R].sum; } int query(int Now,int L,int R,int K) { if(s[Now].sum<K) return -1; if(L==R) return L; int Mid=(L+R)>>1; if(s[s[Now].L].sum>=K) return query(s[Now].L,L,Mid,K); return query(s[Now].R,Mid+1,R,K-s[s[Now].L].sum); } struct SAM{ int ch[maxn][26],fa[maxn],maxlen[maxn],cnt,last; void init() { rep(i,1,tot) s[i].L=s[i].R=s[i].sum=0; rep(i,0,cnt) G[i].clear(),rt[i]=0; cnt=last=1; tot=0; memset(ch[1],0,sizeof(ch[1])); } void add(int x,int id) { int np=++cnt,p=last; last=np; ins(rt[np],1,N,id); maxlen[np]=maxlen[p]+1; memset(ch[np],0,sizeof(ch[np])); while(p&&!ch[p][x]) ch[p][x]=np,p=fa[p]; if(!p) fa[np]=1; else { int q=ch[p][x]; if(maxlen[q]==maxlen[p]+1) fa[np]=q; else { int nq=++cnt; maxlen[nq]=maxlen[p]+1; fa[nq]=fa[q]; fa[q]=fa[np]=nq; memcpy(ch[nq],ch[q],sizeof(ch[q])); while(p&&ch[p][x]==q) ch[p][x]=nq,p=fa[p]; } } } vector<int>G[maxn]; void dfs(int u) { for(int i=0;i<G[u].size();i++){ int v=G[u][i]; f[v][0]=u; dfs(v); merge(rt[u],rt[u],rt[v],1,N); } } void DFS() { rep(i,2,cnt) G[fa[i]].push_back(i); dfs(1); rep(i,1,18) rep(j,1,cnt) f[j][i]=f[f[j][i-1]][i-1]; } }T; void solve() { int L,R,K,len; scanf("%d%d%d",&L,&R,&K); len=R-L+1; int now=pos[R]; for(int i=18;i>=0;i--) if(T.maxlen[f[now][i]]>=len) now=f[now][i]; int ans=query(rt[now],1,N,K); if(ans!=-1) ans=ans-len+1; printf("%d\n",ans); } int main() { int Case; scanf("%d",&Case); while(Case--){ T.init(); scanf("%d%d",&N,&Q); scanf("%s",c+1); rep(i,1,N) T.add(c[i]-'a',i),pos[i]=T.last; T.DFS(); while(Q--) solve(); } return 0; }
D:path HDU - 6705
题意:给定有向图,然后对走路没有限制,包括起点,终点,是否重复走,都是自由的。 求第K长路径。 K<=5e4;(内存比较小)
思路:显然是个比较水的BFS,可以用优先队列来搞,但是内存不够。所以用multiset,这样如果set.size()>K,可以删除尾巴。 看似O(NlogN)的复杂度,却卡了。
是这样的,如果菊花图,边比较多,那么复杂度就接近(KM)了,显然GG,那么我们可以把后续节点按照边权排序,然后每次取前面几个小的即可。
(即是set里面可以删去尾巴,图里面的G[]的尾巴也可以删一部分。不然很容易GG。
#include<bits/stdc++.h> #define f first #define s second #define ll long long #define pii pair<ll,int> #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=250010; int Mx,tot,N,fcy[maxn]; ll ans[maxn]; vector<pair<int,int> >G[maxn]; void read(int &x){ x=0; char c=getchar(); while(c>'9'||c<'0') c=getchar(); while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar(); } void SPFA() { multiset<pii>q; tot=0;ll FCY=0;int SZ=0; multiset<pii>::iterator it; rep(i,1,N) { for(int j=0;j<G[i].size();j++){ q.insert(make_pair(G[i][j].first,G[i][j].second)); SZ++; if(SZ>Mx) q.erase(--q.end()),SZ--; } } while(SZ){ pii now=*q.begin(); int u=now.second; q.erase(q.begin()); SZ--; ans[++tot]=now.first; if(tot>=Mx) return ; for(int i=0;i<G[u].size();i++){ if(SZ&&SZ+tot==Mx&&now.f+G[u][i].f>(*(--q.end())).f) break; int v=G[u][i].second; q.insert(pii{now.first+G[u][i].first,v}); SZ++; while(SZ>Mx-tot) q.erase(--q.end()),SZ--; } } } int main() { int T,M,Q,u,v,w; scanf("%d",&T); while(T--){ scanf("%d%d%d",&N,&M,&Q); rep(i,1,N) G[i].clear(); Mx=0; rep(i,1,M){ read(u); read(v); read(w); G[u].push_back(make_pair(w,v)); } rep(i,1,N) sort(G[i].begin(),G[i].end()); rep(i,1,Q) { read(fcy[i]); Mx=max(Mx,fcy[i]); } SPFA(); rep(i,1,Q) printf("%lld\n",ans[fcy[i]]); } return 0; }
E: huntian oy HDU - 6706
题意:求题意中的互质对数量。
思路:反演一下。
#include <bits/stdc++.h> #include<tr1/unordered_map> #define Accepted 0 using namespace std; typedef long long ll; const int MOD = 1e9 + 7; const int maxn = 5e6 + 10; bool not_prime[maxn]; int prime[maxn / 10]; ll phi[maxn]; void init() { int n = 5000000; int tot = 0; not_prime[1] = 1; phi[1] = 1; for(int i = 2; i <= n; i++) { if(!not_prime[i])prime[tot++] = i, phi[i] = i - 1; for(int j = 0; j < tot && 1LL * prime[j] * i <= n; j++) { not_prime[prime[j] * i] = 1; if(i % prime[j] == 0) { phi[i * prime[j]] = phi[i] * prime[j]; break; } else { phi[i * prime[j]] = phi[i] * (prime[j] - 1); } } } phi[1] = 1; for(ll i = 2; i <= n; i++) phi[i] = (i * phi[i] % MOD + phi[i - 1]) % MOD; } tr1::unordered_map<ll, ll>sumphi; const ll inv2 = 500000004; const ll inv6 = 166666668; ll Sum(ll x) { if(x <= 5000000)return phi[x]; if(sumphi[x])return sumphi[x]; ll ans = x * (x + 1) % MOD * (2 * x + 1) % MOD * inv6 % MOD; for(ll l = 2; l <= x; l++) { ll r = x / (x / l); ll tmp = (l + r) * (r - l + 1) % MOD * inv2 % MOD * Sum(x / l) % MOD; ans = (ans + MOD - tmp) % MOD; l = r; } return sumphi[x] = ans; } int main() { init(); int T; scanf("%d", &T); while(T--) { ll n, a, b, ans = 0; scanf("%lld%lld%lld", &n, &a, &b); printf("%lld\n", (Sum(n) + MOD - 1) * inv2 % MOD); } return Accepted; }
F:Shuffle Card HDU - 6707
题意:给定N个数的排列,然后M次操作,每次把给定的数放到排列的最前面。求最后的排列。
思路:如果一个数多次放到前面,那么最后一次是有影响的操作。 所以我们倒叙操作,维护一个queue,如果是第一次操作,就处理,放到队尾。然后把没有操作过的数按原来顺序插入即可。
#include<bits/stdc++.h> #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=200010; int Laxt[maxn],Next[maxn],To[maxn],cnt; void add(int u,int v) { Next[++cnt]=Laxt[u]; Laxt[u]=cnt; To[cnt]=v; } int a[maxn],vis[maxn],p[maxn],ans[maxn],tot; int main() { int T,N,M; scanf("%d%d",&N,&M); rep(i,1,N) scanf("%d",&a[i]); rep(i,1,M) scanf("%d",&p[i]); for(int i=M;i>=1;i--){ if(vis[p[i]]) continue; ans[++tot]=p[i]; vis[p[i]]=1; } rep(i,1,N) if(!vis[a[i]]) ans[++tot]=a[i]; rep(i,1,N) printf("%d ",ans[i]); return 0; }
G: Windows Of CCPC HDU - 6708
签到题。
#include <bits/stdc++.h> #define Accepted 0 using namespace std; typedef long long ll; const int maxn = 1e5 + 10; bool s[1030][1030]; void dfs(int x, int y, int d, bool f) { if(d == 2) { if(f) { s[x][y] = s[x][y + 1] = s[x + 1][y + 1] = 1; s[x + 1][y] = 0; } else { s[x][y] = s[x][y + 1] = s[x + 1][y + 1] = 0; s[x + 1][y] = 1; } } else { d /= 2; dfs(x, y, d, f); dfs(x, y + d, d, f); dfs(x + d, y, d, !f); dfs(x + d, y + d, d, f); } } int main() { int T, n; scanf("%d", &T); while(T--) { scanf("%d", &n); dfs(0, 0, (1 << n), 1); for(int i = 0; i < (1 << n); i++) { for(int j = 0; j < (1 << n); j++) if(s[i][j])putchar('C'); else putchar('P'); putchar('\n'); } } return Accepted; }
H: Fishing Master HDU - 6709
题意:有一个锅,一个鱼竿,钓鱼的时间的固定的K,每个鱼煮的时间是a[]。 同时最多钓一个鱼,同时最多煮一个鱼,问最短的时间,把鱼都钓起来煮好。
思路:按时间倒叙排序。 然后处理就行了。 但是做着会发现,这样显然还不够。 我们需要想办法把a[]%K之后在操作。
#include<bits/stdc++.h> #define ll long long using namespace std; int T,n,k,t[100010],cnt; long long ans; template<class T> inline void read(T&a){ char c=getchar(); for(a=0;(c<'0'||c>'9')&&c!='-';c=getchar()); bool f=0;if(c=='-')f=1,c=getchar(); for(;c>='0'&&c<='9';c=getchar())a=a*10+c-'0'; if(f)a=-a; } int main(){ for(read(T);T--;){ ans=cnt=0;read(n);read(k); for(register int i=1;i<=n;i++){ read(t[i]),ans+=t[i]+k; cnt+=t[i]/k;t[i]%=k; } ans-=(long long)k*min(cnt,n-1); int j=n-1-min(cnt,n-1); sort(t+1,t+1+n); for(int i=n;i>=n-j+1;i--)ans-=t[i]; printf("%lld\n",ans); } return 0; }