XVII Open Cup named after E.V. Pankratiev. GP of Moscow Workshops
A. Centroid Tree
枚举至多两个重心作为根,检查对于每个点是否都满足$2size[x]\leq size[father[x]]$即可。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #define MS(x, y) memset(x, y, sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 1e5 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int casenum, casei; int n, m; vector<int>a[N]; int root; int treesz; int sz[N]; int mxp[N]; bool done[N]; int v[N]; bool FLAG; vector<int>vt; int getsz(int x, int fa) { sz[x] = 1; for (auto y : a[x])if (y != fa && !done[y]) { sz[x] += getsz(y, x); } return sz[x]; } void getroot(int x, int fa) { mxp[x] = treesz - sz[x];// root = 0; for (auto y : a[x])if (y != fa && !done[y]) { getroot(y, x); gmax(mxp[x], sz[y]); } if (mxp[x] < mxp[root]) { root = x; vt.clear(); vt.push_back(x); } else if (mxp[x] == mxp[root]) { vt.push_back(x); } } bool check(int x, int fa) { sz[x] = 1; for (auto y : a[x]) { if (y == fa)continue; if (!check(y, x))return 0; sz[x] += sz[y]; } for (auto y : a[x]) { if (y == fa)continue; if (sz[y] * 2 > sz[x])return 0; } return 1; } bool dfz(int x) { vt.clear(); treesz = getsz(x, 0); getroot(x, root = 0); for (auto y : vt) { if (check(y, 0))return 1; } return 0; } int main() { //fre(); mxp[0] = inf; scanf("%d%d", &casenum, &n); for (casei = 1; casei <= casenum; ++casei) { for (int i = 1; i <= n; ++i) { done[i] = 0; a[i].clear(); } for (int i = 1; i < n; ++i) { int x, y; scanf("%d%d", &x, &y); a[x].push_back(y); a[y].push_back(x); } puts(dfz(1) ? "Centroid" : "Random"); } return 0; } /* 2 7 1 2 2 3 3 4 4 5 5 6 6 7 1 2 1 3 2 4 2 5 3 6 3 7 1 7 1 2 1 3 2 4 2 5 3 6 3 7 1 4 1 2 2 3 3 4 */
B. Completely Multiplicative Function
爆搜每个质数是$1$还是$-1$,加上前$n$项的前缀和的绝对值必须小于$\log n$的剪枝即可通过。
更好的做法:对于质数$p$,若$p\bmod 3=1$,则填$1$,否则填$-1$。
#include<cstdio> #include<algorithm> using namespace std; const int N = 1e6 + 10; char mu[N] = { 0,1,1,-1,1,1,-1,-1,1,1,1,-1,-1,1,-1,-1,1,1,1,-1,1,1,-1,-1,-1,1,1,-1,-1,1,-1,1,1,1,1,-1,1,-1,-1,-1,1,1,1,-1,-1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,-1,1,1,1,-1,-1,1,-1,1,1,1,-1,1,1,-1,-1,1,1,-1,-1,-1,1,-1,-1,1,1,1,-1,1,1,-1,-1,-1,1,1,-1,-1,-1,1,-1,-1,1,1,-1,1,1,-1,1,1,1,-1,1,-1,-1,-1,1,-1,-1,1,-1,1,1,1,-1,-1,1,-1,-1,1,1,-1,-1,1,1,1,-1,1,1,-1,-1,1,1,1,-1,-1,-1,-1,-1,1,1,1,-1,-1,1,-1,-1,-1,1,1,1,-1,-1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,1,-1,-1,1,-1,-1,-1,-1,1,1,1,1,-1,1,-1,-1,-1,-1,1,1,-1,1,-1,1,1,-1,1,-1,-1,-1,1,1,1,-1,-1,1,1,-1,1,1,1,-1,-1,1,1,-1,-1,-1,-1,-1,-1,1,1,1,-1,1,-1,1,1,-1,-1,-1,1,-1,1,1,1,1,-1,1,-1,1,1,-1,-1,1,-1,-1,1,1,1,-1,-1,1,-1,-1,1,-1,1,1,1,1,-1,-1,1,-1,1,-1,-1,1,-1,-1,1,1,1,-1,1,1,-1,1,-1,1,-1,-1,-1,1,-1,-1,1,1,1,-1,1,-1,-1,1,-1,1,1,-1,-1,1,-1,-1,-1,-1,1,1,1,-1,1,1,-1,1,-1,-1,-1,1,1,-1,1,-1,1,-1,1,1,-1,1,1,-1,1,1,-1,-1,-1,-1,1,-1,1,1,1,-1,-1,-1,-1,1,1,1,-1,1,-1,-1,-1,1,-1,-1,1,1,1,-1,1,1,1,-1,-1,1,1,1,-1,1,-1,1,-1,-1,-1,-1,1,1,1,-1,-1,1,1,-1,-1,1,1,-1,1,1,-1,-1,1,1,-1,-1,-1,-1,-1,-1,1,-1,1,1,1,1,-1,1,-1,-1,1,-1,1,-1,-1,-1,1,1,1,-1,1,-1,-1,1,-1,1,1,1,1,1,-1,1,-1,1,-1,-1,-1,1,-1,-1,-1,1,1,-1,1,1,1,-1,-1,1,1,-1,-1,1,1,-1,1,1,-1,-1,-1,-1,-1,-1,1,-1,-1,-1,1,1,1,1,1,1,1,-1,-1,-1,1,1,-1,-1,1,-1,1,1,-1,1,-1,1,1,-1,-1,1,-1,-1,1,1,1,-1,1,1,-1,-1,-1,1,1,-1,-1,-1,-1,-1,1,1,-1,1,1,-1,1,-1,1,-1,1,-1,-1,1,-1,1,1,1,-1,1,1,1,-1,1,-1,-1,1,-1,-1,1,-1,-1,1,-1,1,1,1,-1,-1,-1,1,1,1,1,-1,1,1,-1,-1,1,1,-1,-1,-1,-1,-1,-1,1,1,1,-1,-1,-1,-1,1,1,1,-1,1,1,-1,1,1,1,-1,1,-1,-1,1,1,-1,-1,1,-1,1,1,-1,1,-1,1,1,-1,-1,1,-1,1,-1,1,-1,1,1,-1,1,-1,1,-1,-1,-1,1,1,1,-1,-1,1,1,-1,-1,-1,-1,1,-1,1,1,-1,1,1,-1,-1,1,1,-1,-1,1,1,-1,-1,1,-1,1,1,-1,-1,1,-1,1,1,-1,1,1,-1,1,-1,-1,1,-1,-1,-1,-1,-1,1,1,1,-1,-1,1,1,1,-1,1,-1,-1,1,-1,1,-1,1,-1,-1,1,-1,1,1,1,-1,-1,1,1,1,-1,1,-1,1,-1,-1,1,-1,-1,1,-1,-1,1,-1,1,-1,1,-1,-1,1,1,-1,1,-1,1,1,-1,1,-1,1,1,-1,1,1,1,-1,-1,1,1,-1,-1,1,1,-1,-1,1,-1,-1,-1,-1,-1,1,1,1,1,-1,1,1,-1,-1,-1,-1,1,1,1,1,-1,1,-1,-1,1,1,1,-1,-1,1,1,-1,1,-1,-1,1,-1,-1,1,-1,1,1,-1,1,-1,1,-1,-1,-1,1,-1,1,-1,1,1,1,-1,-1,1,1,1,-1,1,-1,1,-1,-1,1,1,-1,-1,1,-1,-1,1,1 }; int f[N]; int first[N]; int fac[N]; int prim[N], pnum; int n; int lg2[N]; void check() { for (int i = 1; i <= n; i++) f[i] = f[i - 1] + mu[i]; for (int i = 1; i <= n; i++)if (abs(f[i]) > 20) { printf("f[%d] = %d\n", i, f[i]); while (1); } for (int i = 1; i <= n; ++i) { for (int j = 1; i * j <= n; ++j) { if (mu[i * j] != mu[i] * mu[j]) { printf("%d %d %d %d %d %d\n", i, j, i *j, mu[i], mu[j], mu[i * j]); puts("ERROR"); while (1); } } } } bool FLAG; inline char abs(char x) { return x > 0 ? x : -x; } void dfs(int x,char y) { if (x > n) { //puts("YES!"); FLAG = 1; return; } while (fac[x]) { mu[x] = mu[fac[x]] * mu[first[x]]; y+=mu[x]; if (abs(y) > lg2[x])return; ++x; } if (x > n) { //puts("YES!"); FLAG = 1; return; } { mu[x] = 1; y++; if (abs(y) <= lg2[x]) { dfs(x + 1,y); } if (FLAG)return; mu[x] = -1; y -= 2; if (abs(y) <= lg2[x]) { dfs(x + 1,y); } } } void getprime() { fac[1] = 1; for (int i = 2; i < N; i++) { if (fac[i])continue; prim[pnum++] = i; for (int j = i + i; j < N; j += i) { fac[j] = i; } } } int main() { getprime(); //printf("%d\n", pnum); n = 1e6; fac[n + 1] = 0; lg2[1] = 1; for (int i = 2; i <= n; ++i) { lg2[i] = lg2[i / 2] + 1; if (fac[i]) { first[i] = i / fac[i]; } } for (int i = 1; i <= 820; ++i)f[i] = f[i - 1] + mu[i]; dfs(821,f[820]); //check(); //for (int i = 1; i <= 3000; i++) printf("%d,", mu[i]); scanf("%d", &n); //n = 10; //for (int i = 1; i <= n; i++)if(!fac[i]) printf("%d %d\n", i,mu[i]); for (int i = 1; i <= n; i++)printf("%d%c", mu[i], i == n ? '\n' : ' '); return 0; }
C. Even and Odd
求出DFS生成树,只保留树边。
对于每个基环,若它是奇环,那么上面的树边都不能保留;若它是偶环,那么若它与某个奇环相交,将会得到更大的奇环,这些边也不能保留。
用并查集维护每个点向上第一条未删去的树边,树状数组判断路径上是否存在奇环,迭代删除$O(\log n)$轮即可。如果直接用并查集维护有交的树边集合,那么只要集合内存在基本奇环则要删除,可以做到更优秀的复杂度。
预处理结束后,对于每个连通块,当成树统计答案即可。
时间复杂度$O(n\log^2n)$。
#include<cstdio> const int N=200010,M=400010; int n,m,i,x,y,g[N],v[M],nxt[M],ed; int d[N],f[N],vis[N],dfn; int fa[N],c[N][2]; int dep[N],gg[N]; int q[N],V[M],NXT[M],ED; int st[N],en[N],lim,bit[M]; long long ans[2]; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} int G(int x){return gg[x]==x?x:gg[x]=G(gg[x]);} inline void ADD(int x,int y){V[++ED]=y;NXT[ED]=q[x];q[x]=ED;} inline void modify(int x,int p){for(;x<=lim;x+=x&-x)bit[x]+=p;} inline int ask(int x){int t=0;for(;x;x-=x&-x)t+=bit[x];return t;} inline void go(int x,int y){ //printf("go %d %d\n",x,y); while(1){ x=G(x); if(dep[x]<=dep[y])return; //printf("del %d\n",x); modify(st[x],1); modify(en[x],-1); gg[x]=f[x]; } } void dfs1(int x,int y){ f[x]=y; vis[x]=++dfn; d[x]=d[y]^1; dep[x]=dep[y]+1; gg[x]=x; st[x]=++lim; for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(u==y)continue; if(!vis[u]){ //printf("%d->%d\n",x,u); dfs1(u,x); } } en[x]=++lim; } void dfs2(int x,int y){ vis[x]=++dfn; for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(u==y)continue; if(!vis[u]){ dfs2(u,x); }else if(vis[u]<vis[x]){ if(d[u]==d[x]){ //printf("odd %d %d\n",x,u); go(x,u); }else{ ADD(u,x); } } } for(int i=q[x];i;i=NXT[i]){ int u=V[i]; //printf("even %d %d asku=%d askx=%d\n",u,x,ask(st[u]),ask(st[x])); if(ask(st[u])>ask(st[x]))go(u,x); } } int F(int x){return fa[x]==x?x:fa[x]=F(fa[x]);} inline void merge(int x,int y){ //printf("merge %d %d\n",x,y); x=F(x),y=F(y); fa[x]=y; ans[0]+=1LL*c[x][0]*c[y][0]; ans[0]+=1LL*c[x][1]*c[y][1]; ans[1]+=1LL*c[x][0]*c[y][1]; ans[1]+=1LL*c[x][1]*c[y][0]; c[y][0]+=c[x][0]; c[y][1]+=c[x][1]; } int main(){ scanf("%d%d",&n,&m); for(i=1;i<=m;i++){ scanf("%d%d",&x,&y); add(x,y),add(y,x); } dfs1(1,0); for(i=1;i<=n;i++)vis[i]=0; for(int _=10;_;_--){ for(i=1;i<=n;i++)vis[i]=q[i]=0; dfn=ED=0; dfs2(1,0); } for(i=1;i<=n;i++)fa[i]=i,c[i][d[i]]=1; for(i=2;i<=n;i++)if(gg[i]==i)merge(i,f[i]); printf("%lld %lld",ans[0],ans[1]); } /* 4 3 1 2 1 3 1 4 4 4 1 2 2 3 3 4 4 2 5 6 1 2 2 3 3 4 4 5 1 4 3 5 4 5 1 2 1 3 2 4 3 4 2 3 6 8 1 2 1 3 2 4 3 4 3 5 4 5 4 6 5 6 */
D. Great Again
设$f[i]$表示前$i$个人分组的最大得分,枚举当前段的得分,线段树优化转移即可。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> #include<vector> #include<set> using namespace std; const int N=300010,M=2222222; int n,m,j,L,R,i,lim,tmp,a[N],f[N]; int v[M]; multiset<int>T[N*2]; void build(int x,int a,int b){ v[x]=-M; if(a==b)return; int mid=(a+b)>>1; build(x<<1,a,mid),build(x<<1|1,mid+1,b); } inline void up(int x){v[x]=max(v[x<<1],v[x<<1|1]);} void ins(int x,int a,int b,int c,int p){ if(a==b){ T[a].insert(p); v[x]=*T[a].rbegin(); return; } int mid=(a+b)>>1; if(c<=mid)ins(x<<1,a,mid,c,p); else ins(x<<1|1,mid+1,b,c,p); up(x); } void del(int x,int a,int b,int c,int p){ if(a==b){ T[a].erase(T[a].find(p)); if(T[a].empty())v[x]=-M;else v[x]=*T[a].rbegin(); return; } int mid=(a+b)>>1; if(c<=mid)del(x<<1,a,mid,c,p); else del(x<<1|1,mid+1,b,c,p); up(x); } int ask(int x,int a,int b,int c,int d){ if(c<=a&&b<=d)return v[x]; int mid=(a+b)>>1,t=-M; if(c<=mid)t=ask(x<<1,a,mid,c,d); if(d>mid)t=max(t,ask(x<<1|1,mid+1,b,c,d)); return t; } int main(){ scanf("%d%d%d",&n,&L,&R); for(i=1;i<=n;i++){ scanf("%d",&a[i]); a[i]+=a[i-1]; } m=n+n; for(i=0;i<=n;i++)a[i]+=n;//0..m build(1,0,m); for(i=1,j=0;i<=n;i++){ if(i>=L)ins(1,0,m,a[i-L],f[i-L]); if(i-R-1>=0)del(1,0,m,a[i-R-1],f[i-R-1]); f[i]=ask(1,0,m,a[i],a[i]); if(a[i]>0)f[i]=max(f[i],ask(1,0,m,0,a[i]-1)+1); if(a[i]<m)f[i]=max(f[i],ask(1,0,m,a[i]+1,m)-1); //printf("f[%d]=%d\n",i,f[i]); } if(f[n]<-M/2)puts("Impossible");else printf("%d",f[n]); }
E. Jumping is Fun
每个点不管跳多少步,能到达的范围必然是一个区间。
设$f[i][j]$表示$j$点跳$2^i$步能到的范围,可以用线段树求出。
从高到低枚举答案的二进制的每一位,利用$f$数组求出范围,然后枚举$x$,判断是否存在$y$满足$x$与$y$都不能相互到达即可。
时间复杂度$O(n\log^2n)$。
#include<cstdio> #include<algorithm> using namespace std; const int N=200010,M=530000,K=18; int n,i,j,x,pre[N],suf[N],ans; struct P{ int x,y; P(){} P(int _x,int _y){x=_x,y=_y;} P operator+(const P&b){return P(min(x,b.x),max(y,b.y));} }f[K][N],v[K][M],a[N],b[N]; void build(int o,int x,int a,int b){ if(a==b){ v[o][x]=f[o][a]; return; } int mid=(a+b)>>1; build(o,x<<1,a,mid),build(o,x<<1|1,mid+1,b); v[o][x]=v[o][x<<1]+v[o][x<<1|1]; } P ask(int o,int x,int a,int b,int c,int d){ if(c<=a&&b<=d)return v[o][x]; int mid=(a+b)>>1; P t(N,1); if(c<=mid)t=ask(o,x<<1,a,mid,c,d); if(d>mid)t=t+ask(o,x<<1|1,mid+1,b,c,d); return t; } inline P go(int o,P b){ //if(b.x<1||b.y>n)puts("ERROR"); return ask(o,1,1,n,b.x,b.y); } inline bool check(int o){ //printf("now check %d\n",o); int i; for(i=1;i<=n;i++)b[i]=go(o,a[i]); pre[0]=N; for(i=1;i<=n;i++)pre[i]=min(pre[i-1],b[i].y); suf[n+1]=0; for(i=n;i;i--)suf[i]=max(suf[i+1],b[i].x); for(i=1;i<=n;i++){ if(pre[b[i].x-1]<i){ //printf("to pre x=%d pre[%d]=%d\n",i,b[i].x-1,pre[b[i].x-1]); return 1; } if(suf[b[i].y+1]>i){ //printf("to suf x=%d\n",i); return 1; } } return 0; } int main(){ scanf("%d",&n); for(i=1;i<=n;i++){ scanf("%d",&x); f[0][i].x=max(1,i-x); f[0][i].y=min(n,i+x); } build(0,1,1,n); for(i=1;i<K;i++){ for(j=1;j<=n;j++){ f[i][j]=go(i-1,f[i-1][j]); //printf("f[%d][%d]=%d %d\n",i,j,f[i][j].x,f[i][j].y); } build(i,1,1,n); } for(i=1;i<=n;i++){ a[i]=P(i,i); //printf("->%d %d %d\n",i,a[i].x,a[i].y); } for(i=K-1;~i;i--){ if(check(i)){ //printf("checkok %d\n",i); //for(j=1;j<=n;j++)printf("a[%d]=%d %d\n",j,a[j].x,a[j].y); for(j=1;j<=n;j++)a[j]=b[j]; //for(j=1;j<=n;j++)printf("b[%d]=%d %d\n",j,a[j].x,a[j].y); ans|=1<<i; } } printf("%d",ans+1); } /* 8 7 1 1 1 1 1 1 7 10 2 2 1 2 2 1 2 2 1 2 */
F. Online LCS
将两个串插入同一个后缀自动机,同时维护$v[i][j]$表示节点$i$表示的子串集合是否在串$j$中出现过。
每次加入新的字符的时候,将对应节点到根路径上所有$v$都标记为$1$,当$v[i][0]$与$v[i][1]$同时为$1$时,可以用它的最大长度去更新最长公共子串的长度。
注意到每个点在每种串中只需要被标记一次,故发现标记过则退出即可。
时间复杂度$O(n)$。
#include<cstdio> #include<cstring> const int N=5000010;//5e6 int tot=1,last[2]={1,1},pre[N*2],son[N*2][2],ml[N*2];bool v[N*2][2]; int n,i,ans;long long sum;char a[N]; inline void extend(int o,int w){ int p=++tot,x=last[o],r,q; for(ml[last[o]=p]=ml[x]+1;x&&!son[x][w];x=pre[x])son[x][w]=p; if(!x)pre[p]=1; else if(ml[x]+1==ml[q=son[x][w]])pre[p]=q; else{ pre[r=++tot]=pre[q];memcpy(son[r],son[q],sizeof son[r]); v[r][0]=v[q][0]; v[r][1]=v[q][1]; ml[r]=ml[x]+1;pre[p]=pre[q]=r; for(;x&&son[x][w]==q;x=pre[x])son[x][w]=r; } while(p&&!v[p][o]){ v[p][o]=1; if(v[p][o^1]&&ml[p]>ans)ans=ml[p]; p=pre[p]; } } int main(){ scanf("%d%s",&n,a); for(i=0;i<n;i++){ extend(((a[i]-'0')^ans)&1,(((a[i]-'0')^ans)/2)&1); sum+=ans; } printf("%lld",sum); } /* 5 0 0 0 1 1 0 1 0 1 1 */
G. Brawling
只有左侧朝左的若干人以及右侧朝右的若干人不能保留。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #define MS(x, y) memset(x, y, sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 1e6 + 10, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int casenum, casei; char s[N]; int n; int solve() { int l = 1; while(l <= n && s[l] != 'R')++l; int r = n; while(r >= 1 && s[r] != 'L')--r; //a[l] = 'L', a[r] = 'R' if(l < r) { int sub = r - l + 1 - 1; return n - sub; } else { return n; } } int main() { while(~scanf("%s", s + 1)) { n = strlen(s + 1); printf("%d\n", solve()); } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
H. I Spy
随机选择一个点$P=(x,y)$,在附近找一个辅助点$Q=(x+1,y)$。
因为随机选择,故可以认为对于$P$和$Q$,没有任意两个点离它们的距离相同。
二分半径求出离$P$和$Q$最近的两个点到它们的距离$R_1,R_2$,得到两个圆,在交点附近检查是否存在点即可。
如此反复,即可找出所有点的位置。
#include<stdio.h> #include<iostream> #include<string.h> #include<string> #include<ctype.h> #include<math.h> #include<set> #include<map> #include<vector> #include<queue> #include<bitset> #include<algorithm> #include<time.h> using namespace std; void fre() { freopen("c://test//input.in", "r", stdin); freopen("c://test//output.out", "w", stdout); } #define MS(x, y) memset(x, y, sizeof(x)) #define ls o<<1 #define rs o<<1|1 typedef long long LL; typedef unsigned long long UL; typedef unsigned int UI; template <class T1, class T2>inline void gmax(T1 &a, T2 b) { if (b > a)a = b; } template <class T1, class T2>inline void gmin(T1 &a, T2 b) { if (b < a)a = b; } const int N = 0, M = 0, Z = 1e9 + 7, inf = 0x3f3f3f3f; template <class T1, class T2>inline void gadd(T1 &a, T2 b) { a = (a + b) % Z; } int casenum, casei; int n; LL X0, Y0; LL X1, Y1; set< pair<LL, LL> >know; LL sqr(LL X) { return X * X; } bool IN(LL X0, LL Y0, LL X1, LL Y1, LL R) { return sqr(X0 - X1) + sqr(Y0 - Y1) <= R; } bool check(LL X, LL Y, LL R) { printf("? %lld %lld %lld\n", X, Y, R); fflush(stdout); int num; scanf("%d", &num); for (auto it : know) { if (IN(it.first, it.second, X, Y, R))--num; } return num; } LL GETR(LL X, LL Y) { LL L = 0; LL R = 1e13; while (L < R) { LL MID = (L + R >> 1); if (check(X, Y, MID))R = MID; else L = MID + 1; } return L; } namespace YUAN { struct point { double x, y; point() {} point(double a, double b) : x(a), y(b) {} friend point operator + (const point &a, const point &b) { return point(a.x + b.x, a.y + b.y); } friend point operator - (const point &a, const point &b) { return point(a.x - b.x, a.y - b.y); } friend point operator * (const point &a, const double &b) { return point(a.x * b, a.y * b); } friend point operator * (const double a, const point &b) { return point(a * b.x, a * b.y); } friend point operator / (const point &a, const double &b) { return point(a.x / b, a.y / b); } double norm() { return sqrt(sqr(x) + sqr(y)); } }; point rotate(const point &p, double cost, double sint) { double x = p.x, y = p.y; return point(x * cost - y * sint, x * sint + y * cost); } // 圆与圆求交, ap,bp 两个圆的圆心, ar,br 两个圆的半径。 输出两个交点(要先确认两圆存在交点) pair<point, point> crosspoint(point ap, double ar, point bp, double br) { double d = (ap - bp).norm(); double cost = (ar * ar + d * d - br * br) / (2 * ar * d); double sint = sqrt(1. - cost * cost); point v = (bp - ap) / (bp - ap).norm() * ar; return make_pair(ap + rotate(v, cost, -sint), ap + rotate(v, cost, sint)); } }using namespace YUAN; void check_ans(LL X, LL Y) { for (int i = -2; i <= 2; ++i) { for (int j = -2; j <= 2; ++j) { LL x = X + i; LL y = Y + j; if (abs(x) > 1e6 || abs(y) > 1e6)continue; if (check(x, y, 0)) { know.insert({ x,y }); } } } } int main() { X0 = rand() * rand() % 900000; Y0 = rand() * rand() % 900000; X1 = X0; Y1 = Y0 + 1; scanf("%d", &n); while (know.size() < n) { LL R0 = GETR(X0, Y0); LL R1 = GETR(X1, Y1); pair<point, point>ans = crosspoint({ 1.0*X0,1.0*Y0 }, sqrt(R0), { 1.0*X1,1.0*Y1 }, sqrt(R1)); check_ans(ans.first.x, ans.first.y); check_ans(ans.second.x, ans.second.y); } printf("!"); for (auto it : know) { printf(" %lld %lld", it.first, it.second); } puts(""); fflush(stdout); return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 【数据】 */
I. Rage Minimum Query
若修改是往小修改,那么显然可以$O(1)$直接更新全局最小值。
否则若目前是将最小值往大了修改,那么这是小概率事件,直接$O(n)$重算全局最小值即可。
#include<cstdio> typedef unsigned int U; int n,q; U x0,x1,a,b,c,i,x,v[10000010],mi,ans,p=1; inline U nxt(){ U t=x0*a+x1*b+c; x0=x1; x1=t; return x0>>2; } int main(){ scanf("%d%d%u%u%u%u%u",&n,&q,&x0,&x1,&a,&b,&c); for(i=0;i<n;i++)v[i]=~0U>>1; mi=~0U>>1; while(q--){ i=nxt()%n; x=nxt(); if(x<=v[i]){ if(x<mi)mi=x; v[i]=x; }else if(v[i]>mi){ v[i]=x; }else{ v[i]=x; mi=x; for(i=0;i<n;i++)if(v[i]<mi)mi=v[i]; } p*=10099; ans+=p*mi; } printf("%u",ans); }
J. Regular Cake
在正$n$边形与正$m$边形之间紧贴一个正$lcm(n,m)$边形,可以得到答案为:
\[\frac{\cos\left(\frac{\pi}{lcm(n,m)}\right)\tan\left(\frac{\pi}{m}\right)}{\sin\left(\frac{\pi}{n}\right)}\]
#include<cstdio> #include<cmath> typedef long long ll; using namespace std; const double pi=acos(-1.0); ll n,m,k; ll gcd(ll a,ll b){return b?gcd(b,a%b):a;} int main(){ scanf("%lld%lld",&n,&m); k=n*m/gcd(n,m); printf("%.13f",cos(pi/k)*tan(pi/m)/sin(pi/n)); }
K. Piecemaking
树形DP,设$f[i][j]$表示考虑$i$的子树,$i$点颜色为$j$的最小代价。
时间复杂度$O(n)$。
#include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=200010; const ll inf=1LL<<60; int n,m,i,x,y,z,g[N],v[N<<1],w[N<<1],nxt[N<<1],ed; int col[N]; ll f[N][3],h[3],ans; inline void up(ll&a,ll b){a>b?(a=b):0;} inline void add(int x,int y,int z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;} void dfs(int x,int y){ for(int i=0;i<3;i++)f[x][i]=inf; f[x][col[x]]=0; for(int i=g[x];i;i=nxt[i]){ int u=v[i]; if(u==y)continue; dfs(u,x); for(int j=0;j<3;j++)h[j]=inf; for(int j=0;j<3;j++)if(f[x][j]<inf)for(int k=0;k<3;k++)if(f[u][k]<inf){ //cut up(h[j],f[x][j]+f[u][k]+w[i]); //merge if(j&&k&&j!=k)continue; up(h[max(j,k)],f[x][j]+f[u][k]); } for(int j=0;j<3;j++)f[x][j]=h[j]; } } int main(){ scanf("%d",&n); for(i=1;i<n;i++)scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z); scanf("%d",&m); while(m--)scanf("%d",&x),col[x]=1; scanf("%d",&m); while(m--)scanf("%d",&x),col[x]=2; dfs(1,0); ans=inf; for(i=0;i<3;i++)up(ans,f[1][i]); printf("%lld",ans); } /* 6 1 2 5 2 4 4 2 5 1 1 3 2 3 6 7 1 4 2 5 6 */