XVII Open Cup named after E.V. Pankratiev. GP of Two Capitals
A. Artifact Guarding
选出的守卫需要满足$\max(a+b)\leq \sum a$,从小到大枚举每个值作为$\max(a+b)$,在权值线段树上找到最大的若干个$a$即可。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> #include<set> #include<map> using namespace std; typedef long long ll; const int N=100010,M=262150,inf=~0U>>1; int n,i,ans; int cnt[M];ll sum[M]; ll b[N]; struct P{ll x,y;}a[N]; inline bool cmp(const P&a,const P&b){return a.x<b.x;} inline int lower(ll x){ int l=1,r=n,mid,t; while(l<=r)if(b[mid=(l+r)>>1]<=x)l=(t=mid)+1;else r=mid-1; return t; } void ins(int x,int a,int b,int c,ll p){ cnt[x]++; sum[x]+=p; if(a==b)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); } inline int ask(ll k){ if(k>sum[1])return inf; int x=1,a=1,b=n,mid,t=0; while(a<b){ mid=(a+b)>>1; if(k<=sum[x<<1|1]){ a=mid+1; x=x<<1|1; }else{ k-=sum[x<<1|1]; t+=cnt[x<<1|1]; b=mid; x<<=1; } } return t+k/(::b[a])+(k%(::b[a])>0); } int main(){ freopen("artifact.in","r",stdin); freopen("artifact.out","w",stdout); scanf("%d",&n); for(i=1;i<=n;i++){ ll x,y; scanf("%lld%lld",&x,&y); a[i].x=x+y;//b a[i].y=x;//a b[i]=x; } sort(a+1,a+n+1,cmp); sort(b+1,b+n+1); ans=inf; for(i=1;i<=n;i++){ ins(1,1,n,lower(a[i].y),a[i].y); ans=min(ans,ask(a[i].x)); } if(ans==inf)ans=-1; printf("%d",ans); }
B. Book Pages
将行中的空格以及#号都去掉,那么某一行$A$对应原著中某一行$B$的条件是$A$是$B$的子序列,且$A$与$B$串长差不多。
从上到下考虑原著中的每一行,维护一个可能的页码集合,从中逐渐淘汰掉失配的即可。
时间复杂度$O(n^2)$。
#include<cstdio> #include<cstring> #include<string> #include<vector> #include<iostream> using namespace std; const int N=10010; char a[7000010]; int i,j,k,n; int q[N],q2[N],ans[N],pos[N]; int cnt,cnt2,lim; vector<string>b[N]; inline bool valid(char x){ if(x==' ')return 0; if(x>=32&&x<=126)return 1; if(x=='#')return 1; return 0; } inline bool ispar(string t){ if(t.size()<70)return 0; return t[0]=='-'&&t[1]=='-'&&t[2]=='-'&&t[3]=='#'&&t[4]=='#'&&t[5]=='#' &&t[6]=='-'&&t[7]=='-'&&t[8]=='-'&&t[9]=='#'&&t[10]=='#'&&t[11]=='#'; } inline void init(){ int i; cnt=0; for(i=1;i<=n;i++)if(!ans[i])q[++cnt]=i,pos[i]=0; } inline bool check(string A,int x){ if(pos[x]>=b[x].size())return 0; int o=pos[x]++; if(b[x][o].size()>A.size())return 0; if(b[x][o].size()+10<A.size())return 0; for(int i=0,j=0;i<b[x][o].size();i++){ while(j<A.size()&&A[j]!=b[x][o][i])j++; if(j==A.size())return 0; j++; } return 1; } int main(){ freopen("book-pages.in","r",stdin); freopen("book-pages.out","w",stdout); while(gets(a+1)){ int len=strlen(a+1); int l=1,r=len; while(l<=r&&!valid(a[l]))l++; while(l<=r&&!valid(a[r]))r--; //for(i=l;i<=r;i++)putchar(a[i]); if(l>r)continue; string t=""; for(int i=l;i<=r;i++)t.push_back(a[i]); if(ispar(t)){ //newpage n++; continue; } t=""; bool flag=0; for(int i=l;i<=r;i++)if(a[i]!=' '&&a[i]!='#')t.push_back(a[i]),flag=1; if(!flag)continue; b[n].push_back(t); } /*for(i=0;i<=n;i++){ for(j=0;j<b[i].size();j++)cout<<b[i][j]<<endl; cout<<"--END--"<<endl; } puts("DONE");*/ for(i=1;i<=n;i++)ans[i]=0; init(); for(i=0;i<b[0].size();i++){ cnt2=0; int flag=0; for(j=1;j<=cnt;j++){ int x=q[j]; //printf("check %d %d %d\n",i,x,check(b[0][i],x)); if(check(b[0][i],x)){ if(pos[x]==b[x].size()){ flag=x; break; } //puts("PUSH"); q2[++cnt2]=x; } } //printf("cnt2=%d\n",cnt2); if(flag){ ans[flag]=++lim; init(); }else{ for(j=1;j<=cnt2;j++)q[j]=q2[j]; cnt=cnt2; } } for(i=1;i<=n;i++)printf("%d%c",ans[i],i<n?' ':'\n'); } /* rehuitg vnudfj rei436 tgrh rtyhtr 43 m98t43 m9 43645784 9hgfuh fg 235u4398654u9867 g45uy 985498hj54h98yj6 2543y89675498 ghjf8rghfd 436u8934 89rghjre98 gynh9854 hj985hj5849b y68947n8 9 ---###---###---### hj985 hj584 9b y68 94 7n8 9 ---###---###---### 235u4398654u9867 g45uy 9#85498hj5#4h98yj6 2#543y8#9675498 ghjf8#rghfd 436u8934 89rghjre98 gynh9854 ---###---###---### rehtg vnudfj rei4grh rtyhtr 43 mt43 m9 435784 9hgfuh fg */
C. Regular Bracket Sequence
显然两种括号互不影响,故全按照$(((())))$的方式构造即可。
#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("brackets.in", "r", stdin); freopen("brackets.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; char s[110]; int n, l, r; int main() { fre(); scanf("%d%d%d", &n, &l, &r); for(int i = l; i <= (l + r) / 2; i ++){ s[i] = '['; } for(int i = (l + r) / 2 + 1; i <= r; i ++){ s[i] = ']'; } int len = (n - (r - l + 1)) / 2; for(int i = 1; i <= n; i ++){ if(i < l || i > r){ if(len) {s[i] = '('; len --;} else s[i] = ')'; } } printf("%s\n", s + 1); return 0; } /* 【trick&&吐槽】 【题意】 【分析】 【时间复杂度&&优化】 */
D. Brick Counting
旋转坐标系,转化为若$(x,y)$以及$(x,y+1)$都有砖块,则$(x+1,y)$处可以放入一个砖块。
从下到上考虑每个$x$,那么若某个$y$右侧不存在$y+1$,则它不可以存在,用set维护即可。
时间复杂度$O(n\log n)$。
#include<cstdio> #include<algorithm> #include<set> #include<map> using namespace std; typedef long long ll; const int N=300010,inf=~0U>>1; int n,i; ll ans; int X,pos; set<int>T,G; struct P{int x,y;}a[N]; inline bool cmp(const P&a,const P&b){return a.x<b.x;} int main(){ freopen("brick-count.in","r",stdin); freopen("brick-count.out","w",stdout); scanf("%d",&n); for(i=1;i<=n;i++){ int x,y; scanf("%d%d",&x,&y); a[i].x=x; a[i].y=(y-x)/2; } sort(a+1,a+n+1,cmp); pos=1; X=-inf; //for(i=1;i<=n;i++)printf("%d %d\n",a[i].x,a[i].y); while(1){ if(T.size()==0){ if(pos>n)break; X=a[pos].x; } //printf("X=%d:\n",X); set<int>W; W.clear(); for(set<int>::iterator it=G.begin();it!=G.end();it++){ int x=*it; //printf("del %d\n",x); T.erase(x); if(T.find(x-1)!=T.end())W.insert(x-1); } G=W; while(pos<=n&&a[pos].x==X){ int x=a[pos++].y; if(T.find(x)!=T.end())continue; //printf("ins %d\n",x); T.insert(x); if(T.find(x+1)==T.end())G.insert(x); if(T.find(x-1)!=T.end())G.erase(x-1); } ans+=T.size(); X++; } printf("%lld",ans); } /* 7 0 0 0 2 2 0 3 1 1 3 3 5 0 6 */
E. Cross on a Plane
留坑。
F. Lights
显然答案为$1$到$n$的距离的$3$倍,贪心检查是否存在可行解即可。
#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("lights.in", "r", stdin); freopen("lights.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; LL a[N], r[N]; LL solve() { multiset<LL>sot; LL ans = (a[n] - a[1]) * 3; int pos = 1; LL can = a[1] + r[1]; while(pos < n && a[pos + 1] <= can) { ++pos; gmax(can, a[pos] + r[pos]); } if(pos != n)return -1; for(int i = 1; i <= n; ++i) { sot.insert(a[i] - r[i]); } for(int i = 1; i < n; ++i) { sot.erase(sot.find(a[i] - r[i])); if(*sot.begin() > a[i])return -1; //printf("%lld %lld\n", *sot.begin(), a[i]); } return ans; } int main() { fre(); while(~scanf("%d", &n)) { for(int i = 1; i <= n; ++i) { scanf("%lld", &a[i]); scanf("%lld", &r[i]); } printf("%lld\n", solve()); } return 0; } /* 【trick&&吐槽】 【题意】 【分析】 我们需要找到一个延展最远的,且能覆盖到起电的点,去关掉起点 于是—— 我们对于所有能够覆盖起点的点,选取一个右界尽可能远的。 然后接下来怎么做? 我们把左边的所有点都删掉, 【时间复杂度&&优化】 5 1 5 3 1 4 9 7 8 8 4 2 1 1 10 10 2 1 10 10 8 2 1 1000000000 1000000000 999999999 4 1 300 100 100 300 300 600 300 3 1 100 3 2 6 3 */
G. Pigeonhole Principle
留坑。
H. Sophie’s Sets
考虑容斥,枚举哪些集合必然满足,其它随意,枚举这些集合满足两个条件之中的哪一个,求交后用组合数求出方案数即可。
压位存储集合,需要优秀的代码实现,配合剪枝才能通过。
时间复杂度$O(\frac{n3^m}{64})$。
#include<cstdio> typedef unsigned long long ll; const int N=110,M=20,P=1000000007; int n,m,cnt,K,S,i,j,k,x,C[N][N],f[N][N],ans;ll a[M][2],b[M][2],w[2]; void dfsadd(int x,ll A,ll B,ll C,ll D){ if(!A&&!B||!C&&!D)return; if(x==cnt){ //printf("%d %d\n",__builtin_popcountll(A)+__builtin_popcountll(B),__builtin_popcountll(C)+__builtin_popcountll(D)); f[__builtin_popcountll(A)+__builtin_popcountll(B)][__builtin_popcountll(C)+__builtin_popcountll(D)]++; return; } dfsadd(x+1,A&b[x][0],B&b[x][1],C&~b[x][0],D&~b[x][1]); dfsadd(x+1,A&~b[x][0],B&~b[x][1],C&b[x][0],D&b[x][1]); } void dfssub(int x,ll A,ll B,ll C,ll D){ if(!A&&!B||!C&&!D)return; if(x==cnt){ f[__builtin_popcountll(A)+__builtin_popcountll(B)][__builtin_popcountll(C)+__builtin_popcountll(D)]--; return; } dfssub(x+1,A&b[x][0],B&b[x][1],C&~b[x][0],D&~b[x][1]); dfssub(x+1,A&~b[x][0],B&~b[x][1],C&b[x][0],D&b[x][1]); } int main(){ freopen("separating-sets.in","r",stdin); freopen("separating-sets.out","w",stdout); scanf("%d%d%d",&n,&m,&K); for(C[0][0]=i=1;i<=n;i++)for(C[i][0]=j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%P; for(i=0;i<m;i++){ scanf("%d",&k); while(k--){ scanf("%d",&x); x--; a[i][x>>6]^=1ULL<<(x&63); } //printf("->%d %llu\n",i,a[i][0]); } for(i=0;i<n;i++)w[i>>6]^=1ULL<<(i&63); for(S=1;S<1<<m;S++){ for(cnt=i=0;i<m;i++)if(S>>i&1){ b[cnt][0]=a[i][0]; b[cnt][1]=a[i][1]; cnt++; } //printf("S=%d cnt=%d\n",S,cnt); //for(i=0;i<cnt;i++)printf("%llu ",b[i][0]);puts(""); if(cnt&1)dfsadd(0,w[0],w[1],w[0],w[1]);else dfssub(0,w[0],w[1],w[0],w[1]); } for(i=K;i<=n;i++)for(j=K;j<=n;j++)ans=(1LL*C[i][K]*C[j][K]%P*f[i][j]+ans)%P; printf("%d",(ans+P)%P); }
I. Puzzle with Tables
留坑。
J. Travelling to Random Cities
因为$n=100000,m=300000$,且图随机,那么每个点平均连了$6$条边,且两点间最短路一般不会超过$8$。
对于每个询问$S,T$,若两个点不连通,那么显然无解,可以$O(1)$判断。
从$S$和$T$分别爆搜$4$步,那么除去最短路过来的那条边,平均还剩$5$条边,可以得到所有不超过$8$的答案,时间复杂度$O(6\times 5^3)$。
若此时还得不到答案,那么答案超过$8$,是小概率事件,直接$O(n+m)$BFS整张图即可。
#include<cstdio> #include<algorithm> #include<set> #include<map> #include<cstdlib> #include<ctime> #include<vector> using namespace std; typedef long long ll; typedef pair<int,int>P; const int N=100010,M=600010,inf=~0U>>1; int K=4,lim=10000; int n,m,Q,i,g[N],v[M],nxt[M],ed; int h,t,q[N],d[N]; int ans,POS,vis[N]; vector<int>e[N]; int p1,p2,v1[N],v2[N],d1[N],d2[N]; set<P>T; int cnt=0,all; int f[N]; inline void add(int x,int y){v[++ed]=y;nxt[ed]=g[x];g[x]=ed;} int bfs(int S,int T){ int i; for(i=1;i<=n;i++)d[i]=-1; d[q[h=t=1]=S]=0; while(d[T]<0){ int x=q[h++]; for(i=g[x];i;i=nxt[i])if(d[v[i]]<0)d[q[++t]=v[i]]=d[x]+1; } return d[T]; } inline void bfs1(int S){ int i; v1[S]=++p1; d1[q[h=t=1]=S]=0; while(h<=t){ int x=q[h++]; if(d1[x]==K)continue; for(i=g[x];i;i=nxt[i])if(v1[v[i]]<p1){ d1[q[++t]=v[i]]=d1[x]+1; v1[v[i]]=p1; } } all+=t; } inline void bfs2(int S){ int i; v2[S]=++p2; d2[q[h=t=1]=S]=0; while(h<=t){ int x=q[h++]; if(v1[x]==p1&&d1[x]+d2[x]<ans)ans=d1[x]+d2[x]; if(d2[x]==K)continue; for(i=g[x];i;i=nxt[i])if(v2[v[i]]<p1){ d2[q[++t]=v[i]]=d2[x]+1; v2[v[i]]=p1; } } all+=t; } int F(int x){return f[x]==x?x:f[x]=F(f[x]);} void dfs1(int x,int y,int z){ if(vis[x]<POS)vis[x]=POS,d[x]=y;else if(d[x]>y)d[x]=y;else return; if(y==K)return; y++; for(vector<int>::iterator i=e[x].begin();i!=e[x].end();i++) if(*i!=z)dfs1(*i,y,x); } void dfs2(int x,int y,int z){ if(vis[x]==POS&&d[x]+y<ans)ans=d[x]+y; if(y==K||y>=ans)return; y++; for(vector<int>::iterator i=e[x].begin();i!=e[x].end();i++) if(*i!=z)dfs2(*i,y,x); } int main(){ freopen("travelling.in","r",stdin); freopen("travelling.out","w",stdout); srand(time(NULL)); scanf("%d%d",&n,&m); for(i=1;i<=n;i++)f[i]=i; while(m--){ int x,y; scanf("%d%d",&x,&y); /*while(1){ x=rand()%n+1; y=rand()%n+1; if(x==y)continue; if(x>y)swap(x,y); if(T.find(P(x,y))==T.end()){ T.insert(P(x,y)); break; } }*/ if(F(x)!=F(y))f[f[x]]=f[y]; add(x,y),add(y,x); e[x].push_back(y); e[y].push_back(x); } //puts("DONE"); scanf("%d",&Q); while(Q--){ int x,y; scanf("%d%d",&x,&y); //x=rand()%n+1,y=rand()%n+1; if(F(x)!=F(y)){puts("-1");continue;} POS++; cnt=0; //ans=~0U>>1; ans=inf; dfs1(x,0,0); dfs2(y,0,0); if(ans==inf)ans=bfs(x,y); all+=cnt; //printf("cnt=%d\n",cnt); printf("%d\n",ans); } //printf("all=%d\n",all); } /* 6 5 1 2 2 3 1 3 1 4 4 5 5 1 3 4 2 3 5 5 1 4 6 */
K. Cypher
从高位到低位考虑,设$f[i][j]$表示考虑到第$i$位,第$i$位之前部分的和还剩$j$时,最小的$a$是多少,因为当$j>n$时必然无解,故只需要考虑不超过$n$的状态。
时间复杂度$O(n\log a)$。
#include<cstdio> #include<algorithm> typedef long long ll; const ll inf=1LL<<62; using namespace std; const int N=63,M=400010; int n,i,j,k,c[N][2];ll x,S,f[N][M]; inline void up(ll&a,ll b){a>b?(a=b):0;} int main(){ freopen("xor-cypher.in","r",stdin); freopen("xor-cypher.out","w",stdout); scanf("%d%lld",&n,&S); while(n--){ scanf("%lld",&x); for(i=0;i<60;i++)c[i][(x>>i&1)^1]++; } for(i=0;i<=60;i++)for(j=0;j<M;j++)f[i][j]=inf; f[60][0]=0; for(i=59;~i;i--)for(j=0;j<M;j++)if(f[i+1][j]<inf){ for(k=0;k<2;k++){ int t=j*2-c[i][k]+(S>>i&1); if(t>=0&&t<M) up(f[i][t],f[i+1][j]*2+k); } } if(f[0][0]==inf)f[0][0]=-1; printf("%lld",f[0][0]); }