Contest link

link -> gym 103049

Experience

注意精度 Problem F

Problem A. Atomic Energy

Description

完全背包,物品的体积是1,2n 价值是a1,a2an

给出q次询问,每次询问体积恰为k的背包的价值是多少

1n100,q105 1ai109,1k109

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),每次会返回离该点的最近的点的距离的平方

点的范围0x106,0y106

点的个数1n7

返回的距离0d2×1012

Solution

Analysis

官方题解给出了众多解法,这边提出一个简易交互做法。

第一次询问(0,0)

得到返回的值d,考虑枚举以(0,0)为圆心,半径为d的圆上的整点。

注意到d1012,在这个情景下,整点数目不会很多。

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获胜。

2n105

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|S1S2不连通。

保证数据给出一组解。

$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;
}

一个好奇的人