Contest link
Experience
注意精度 Problem F
Problem A. Atomic Energy
Description
完全背包,物品的体积是1,2…n 价值是a1,a2…an
给出q次询问,每次询问体积恰为k的背包的价值是多少
1≤n≤100,q≤105 1≤ai≤109,1≤k≤109
Solution
Analysis
经典题,考虑大数据贪心,小数据DP。
小数据进行完全背包DP,大数据按照密度(akk)贪心。
Attention
进行简单时间计算,DP的上限可以设为105。
Code
#include<bits/stdc++.h> #define rep(i,a,b) for(register int i=a;i<=b;++i) #define il inline #define inc(i,a,b) for(register int i=a;i<=b;++i) #define fo(i,a,b) for(register int i=a;i<=b;++i) #define dec(i,a,b) for(register int i=a;i>=b;--i) #define fd(i,a,b) for(register int i=a;i>=b;--i) #define pb push_back #define re register typedef long long ll; typedef double db; namespace io{ #define gc getchar il int read(){ int x=0;bool f=1;char ch=gc(); if(ch=='-') f=0; while(!isdigit(ch)) ch=gc(); while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=gc(); return f?x:-x; } } using io::read; using namespace std; /*---- head -----*/ #define N 100005 int n, q; ll a[N], f[N]; int main(){ n = read(); q = read(); fo (i, 1, n) a[i] = read(); fo (i, 1, n) f[i] = a[i]; int up = 100000; fo (i, n + 1, up) { f[i] = a[1] + f[i - 1]; fo (j, 1, n) f[i] = std::min(f[i], a[j] + f[i - j]); } fo (i, 1, q) { int now = read(); if (now <= up) printf("%lld\n", f[now]); else { ll ans; fo (k, 1, n) { int x = (now - up) / k; if ((now - up) % k) ++x; ll s = x * a[k] + f[now - x * k]; if (k == 1) ans = s; else ans = std::min(ans, s); } printf("%lld\n", ans); } } return 0; }
Problem D. Dragon Balls
Description
在2维平面的第一象限上(包括坐标轴)有n个点
你可以询问至多1000次,每次询问给出坐标(x,y),每次会返回离该点的最近的点的距离的平方
点的范围0≤x≤106,0≤y≤106
点的个数1≤n≤7
返回的距离0≤d≤2×1012
Solution
Analysis
官方题解给出了众多解法,这边提出一个简易交互做法。
第一次询问(0,0)
得到返回的值d,考虑枚举以(0,0)为圆心,半径为√d的圆上的整点。
注意到d≤1012,在这个情景下,整点数目不会很多。
Attention
询问的点对(x,y)务必保证在数据范围内,否则评测姬会返回TLE。
Code
#include<bits/stdc++.h> #define rep(i,a,b) for(register int i=a;i<=b;++i) #define il inline #define inc(i,a,b) for(register int i=a;i<=b;++i) #define fo(i,a,b) for(register int i=a;i<=b;++i) #define dec(i,a,b) for(register int i=a;i>=b;--i) #define fd(i,a,b) for(register int i=a;i>=b;--i) #define pb push_back #define re register typedef long long ll; typedef double db; namespace io{ #define gc getchar il ll read(){ ll x=0;bool f=1;char ch=gc(); if(ch=='-') f=0; while(!isdigit(ch)) ch=gc(); while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=gc(); return f?x:-x; } } using io::read; using namespace std; /*---- head -----*/ int n; int main(){ n=read(); ll mn=1e6; rep(id,1,n){ puts("0 0"); fflush(stdout); ll d=read(); if(d==0) continue; for(register ll x=0;x*x<=d;++x){ ll y=(ll)(sqrt(d-x*x)+0.1); if((y*y+x*x)!=d) continue; if(x>mn || y>mn) continue; printf("%lld %lld\n",x,y); fflush(stdout); ll d2=read(); if(d2==0) break; } } return 0; }
Problem E. Endgame
Description
在n×n的棋盘上两人进行移动。每次两人可以选择如下操作之一:
1.进行两次给定的移动 2.移动到任意没有棋子的位置 3.不移动
共有n个给定移动模式,负数表示向左或者向上,不能移动到界外
初始状态Alice在ax,bx,Bob在bx,by,Alice先动,如果Alice能抓到Bob,则Alice获胜;
如果Alice能走到一个Bob不能走到的点,则平局在这个点;
其他情况,Bob获胜。
2≤n≤105
Solution
Analysis
对于Alice获胜,直接判断是否能走到
对于平局,考虑平局的点会很多(理由:移动模式的数量级在n,棋盘有n×n个格点)
考虑随机化平局的点,如果能判断就平局;
其他情况Bob获胜。
Attention
待补严谨证明
Code
#include<bits/stdc++.h> #define rep(i,a,b) for(register int i=a;i<=b;++i) #define il inline #define inc(i,a,b) for(register int i=a;i<=b;++i) #define fo(i,a,b) for(register int i=a;i<=b;++i) #define dec(i,a,b) for(register int i=a;i>=b;--i) #define fd(i,a,b) for(register int i=a;i>=b;--i) #define pb push_back #define re register typedef long long ll; typedef double db; namespace io{ #define gc getchar il int read(){ int x=0;bool f=1;char ch=gc(); if(ch=='-') f=0; while(!isdigit(ch)) ch=gc(); while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=gc(); return f?x:-x; } } using io::read; using namespace std; /*---- head -----*/ #define mp make_pair map<pair<int,int> ,bool> table; pair<int,int> a[100010]; int n,ax,ay,bx,by; il bool ok(int dx,int dy,int x,int y){ if(dx==0 && dy==0) return 1; if(table[mp(dx,dy)]) return 1; rep(i,1,n){ int nx=x+a[i].first; int ny=y+a[i].second; if(nx<1 || ny<1 || nx>n || ny>n) continue; if(table[mp(dx-a[i].first,dy-a[i].second)]) return 1; } return 0; } int main(){ n=read(); ax=read(),ay=read(),bx=read(),by=read(); rep(i,1,n){ int dx=read(),dy=read(); table[make_pair(dx,dy)]=1; a[i]=make_pair(dx,dy); } if(ok(bx-ax,by-ay,ax,ay)){ puts("Alice wins");return 0;} int T=500; while(T--){ int x=rand()%n+1,y=rand()%n+1; if(!ok(x-bx,y-by,bx,by)){ printf("tie %d %d",x,y); return 0; } } puts("Bob wins"); return 0; }
Problem J. Joint Excavation
Description
蛮有趣的一个题
给一个n个点m条边的无向连通图。
求构造一种方案。
删除图中的一条简单路径,使剩下的部分可以分成两个集合S1,S2,满足|S1|=|S2|,S1与S2不连通。
保证数据给出一组解。
$1\leq n \leq 210^5,0\leq m \le 210^5$
Solution
Analysis
考虑一个基本性质,无向图的dfs树是没有横叉边,只有返祖边的。如果删除dfs树从根开始的一条路径,那么各个子树一定不互相连通。
此时题目完全等价于:给定一棵树,从根节点删除一条路径,使剩余完整的子树可以分成数量相同的两部分。
考虑一种贪心策略:
对于一个点的轻儿子,我们将分配给S1或者S2(取决于当前谁的|S|更小)
对于一个点的重儿子,我们考虑两种情况,如果分配给集合已经满足题意,就直接return,如果没有我们可以继续这个操作。
Attention
事实上,我们可以通过以下方式来证明为什么这样子操作是对的。
对于一个子树,我们考虑当|S1|≤|S2|就加入到S1中。
这样子每轮操作完,必定保证此时|S1|≥|S2|,每一层都保证这个,并且二者的差会逐渐缩小
对于最后一层,一定有一个单独的叶子节点可以产生贡献(正/负),使得|S1|=|S2|。
Code
#include<bits/stdc++.h> #define rep(i,a,b) for(register int i=a;i<=b;++i) #define il inline #define inc(i,a,b) for(register int i=a;i<=b;++i) #define fo(i,a,b) for(register int i=a;i<=b;++i) #define dec(i,a,b) for(register int i=a;i>=b;--i) #define fd(i,a,b) for(register int i=a;i>=b;--i) #define pb push_back #define re register typedef long long ll; typedef double db; namespace io{ #define gc getchar il int read(){ int x=0;bool f=1;char ch=gc(); if(ch=='-') f=0; while(!isdigit(ch)) ch=gc(); while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=gc(); return f?x:-x; } } using io::read; using namespace std; /*---- head -----*/ const int N=200100,M=200100; struct node{ int sz,v; il bool operator <(const node & other) const{ return sz>other.sz; } };vector<node> g[N]; struct Edge{ int v,nxt; } e[M<<1];int head[N],tot; bool vis[N]; il int dfs(int u){ // cerr<<u<<endl; vis[u]=1; int sz=1; for(register int i=head[u];i;i=e[i].nxt){ int v=e[i].v; if(vis[v]) continue; int sz_v=dfs(v); sz+=sz_v; g[u].push_back((node){sz_v,v}); } sort(g[u].begin(),g[u].end()); return sz; } int suma,sumb,ans,res[N],a[N],b[N],cnt_a,cnt_b; il void solve(int u){ res[++ans]=u; for(register unsigned i=1;i<g[u].size();++i){ int v=g[u][i].v,sz=g[u][i].sz; if(suma<=sumb){ suma+=sz; a[++cnt_a]=v; } else{ sumb+=sz; b[++cnt_b]=v; } } if(g[u].size()){ int v=g[u][0].v,sz=g[u][0].sz; if(suma+sz==sumb){ suma+=sz; a[++cnt_a]=v; return; } if(sumb+sz==suma){ sumb+=sz; b[++cnt_b]=v; return; } solve(v); } } int n,m; il void add_edge(int u,int v){ e[++tot]=(Edge){v,head[u]}; head[u]=tot; } il void print(int u){ printf("%d ",u); for(register unsigned i=0;i<g[u].size();++i){ int v=g[u][i].v; print(v); } } int main(){ n=read();m=read(); rep(i,1,m){ int u=read(),v=read(); add_edge(u,v); add_edge(v,u); } dfs(1); solve(1); printf("%d %d\n",ans,suma); rep(i,1,ans) printf("%d ",res[i]); puts(""); rep(i,1,cnt_a) print(a[i]); puts(""); rep(i,1,cnt_b) print(b[i]); puts(""); return 0; }