Codeforces Round #647 (Div. 1) - Thanks, Algo Muse! Solution(暂缺F)

文章目录

  • 前言
  • A A A
  • B B B
  • C C C
  • D D D
  • E E E

前言

还好没打,不然掉分掉到爽-----实力还是太弱…

A A A

本题主要考察选手的英语阅读能力,我看十几分钟才看懂,呜呜呜

你需要写 n n n篇博客,博客的主题相关性为一张图.

一篇博客的主题为所有相邻已写博客的主题的 mex \text{mex} mex值(不考虑0).

现在已知所有博客的主题,请你安排写作顺序.

直接模拟求 mex \text{mex} mex值即可.

int n,m,t[N],ans[N],cnt,pos[N],val[N],vis[N],num;
struct edge{int y,next;}a[N*2]; int len,last[N];
void ins(int x,int y) {a[++len]=(edge){y,last[x]}; last[x]=len;}

int main() {
	qr(n); qr(m);
	for(int i=1,x,y;i<=m;i++) qr(x),qr(y),ins(x,y),ins(y,x);
	for(int i=1;i<=n;i++) qr(t[i]),val[i]=t[i];
	sort(val+1,val+n+1);
	for(int i=1;i<=n;i++) {
		num++;
		for(int k=last[i];k;k=a[k].next)
			vis[t[a[k].y]]=num;
		 int flag=1;
		 while(flag<=t[i]&&vis[flag]==num) flag++;
		 if(flag^t[i]) puts("-1"),exit(0);
	}
	memset(vis+1,0,n<<2);
	for(int i=1;i<=n;i++) {
		if(vis[t[i]]) ++vis[t[i]];
		else vis[t[i]]=lower_bound(val+1,val+n+1,t[i])-val;
		ans[vis[t[i]]]=i;
	}
	for(int i=1;i<=n;i++) pr1(ans[i]);
	return 0;
}

B B B

已知 n , p , k i ( i ∈ [ 1 , n ] ∩ N ) n,p,k_i(i\in [1,n]\cap \N) n,p,ki(i[1,n]N).

n n n个数的真实值为 p k i p^{k_i} pki.

现在需要把他们划分出2个集合.

使得两者的差最小.

转化一下,相当于每个数前加 ± \pm ±,然后最小化和(得到一个非负数)

考虑从大到小排序, s s s表示当前和(只考虑 p p p进制下的前缀位,即忽略低位)(当指数变化 Δ d \Delta d Δd时, s = s ⋅ p Δ d ) s=s\cdot p^{\Delta d}) s=spΔd).

s > 0 s>0 s>0,则 − . -. .

否则 + . +. +.

s > n s>n s>n,则剩下的全部为 − - .

int n,p,a[N],flag;
ll s,ans;


void del(ll &x,ll y) {x-=y; if(x<0) x+=mod;}
void add(ll &x,ll y) {x+=y; if(x>=mod) x-=mod;}

int main() {
	int _;qr(_); while(_--) {
		qr(n); qr(p); s=flag=ans=0;
		for(int i=1;i<=n;i++) qr(a[i]);
		if(p==1) {pr2(n&1); continue;}
		sort(a+1,a+n+1); a[n+1]=a[n];
		for(int i=n;i>=1;i--) {
			int x=min(a[i+1]-a[i],20);
			if(!flag) while(x) {
				s*=p; x--;
				if(s>=i) {flag=1;break;}
			}
			if(flag) {del(ans,power(p,a[i])); continue;}
			if(s) s--,del(ans,power(p,a[i]));
			else s++,add(ans,power(p,a[i]));
		}
		pr2(ans);
	}
	return 0;
}

C C C

给你 n n n个项链段,让你把它们合成一条项链.

端点用整数表示其美丽值.

当两点合并时,其贡献为 ⌊ l o g ( x ⊕ y ) ⌋ ( 特 别 的 , 对 于 x = y , 贡 献 为 20 ) \lfloor log(x\oplus y)\rfloor(特别的,对于x=y,贡献为20) log(xy)(,x=y,20),

一个项链的美丽值为所有连接的最小值.

枚举答案 i i i,然后进行判断.

两个点能相连当且仅当其后 i i i位相等.

一条项链段相当于沟通了 x & ( 2 i − 1 ) , y & ( 2 i − 1 ) x\&(2^i-1),y \&(2^i-1) x&(2i1),y&(2i1).

那么我们可以把它当做一条边,然后能连成项链当且仅当所有边都能遍历(欧拉图).

所以就变成了判断欧拉图.

#include
#include
#include
#define pb push_back
#define pi pair
#define fi first
#define se second
using namespace std;
const int N=1<<20|10;

int n,a[N][2];

vector<pi> e[N];
bool v[N];
void dfs(int x) {
	v[x]=1;
	for(auto y:e[x])	
		if(!v[y.fi]) dfs(y.fi);
}

int b[N],sta[N],top,ans[N],cnt;
void Euler(int x) {
	top=0; sta[++top]=x;
	while(top) {
		x=sta[top];
		pi y; bool flag=0;
		while(e[x].size()) {
			y=e[x].back();
			e[x].pop_back();
			if(!v[y.se/2]) {flag=1; break;}
		}
		if(!flag) ans[++cnt]=b[top--],ans[cnt+1]=ans[cnt]^1,cnt++;
		else {
			v[y.se/2]=1;
			++top;
			b[top]=y.se;
			sta[top]=y.fi;
		}
	}
	cnt-=2;
	while(cnt) printf("%d ",ans[cnt--]-1);
}

bool check(int t) {
	for(int i=0;i<=t;i++)
		e[i].clear(),v[i]=0;
	for(int i=1;i<=n;i++) {
		int x=a[i][0]&t,y=a[i][1]&t;
		e[x].pb({y,2*i+1});
		e[y].pb({x,2*i});
	}
	int c=0;
	for(int i=0;i<=t;i++) {
		if(e[i].size()&1) return 0;
		if(e[i].size()&&!v[i]) {
			dfs(i),c++;
			if(c>=2) return 0;
		}
	}
	return 1;
}

void qr(int &x) {scanf("%d",&x);}

int main() {
	qr(n);
	for(int i=1;i<=n;i++) qr(a[i][0]),qr(a[i][1]);
	for(int i=20;~i;i--)
		if(check((1<<i)-1)) {
			memset(v,0,sizeof v);
			printf("%d\n",i); 
			int t=(1<<i)-1;
			for(int j=0;j<=t;j++)
				if(e[j].size()) 
					{Euler(j); break;}
			break;
		}
	return 0;
}

D D D

给你 n n n个点.

如果两个点在从原点出发的同一射线上,那么他们的距离为其欧几里得距离.

否则,他们的距离为到原点的距离之和.

现在你需要保留 k k k个其中的点,并最大化所有无序点对的距离之和.

师兄xgc一眼就看出这是一个凸函数,然后秒了~~~,orz.

先讲讲凸函数:

对于定义在整数域上的函数 f ( x ) f(x) f(x), ∀ f ( x ) − f ( x − 1 ) ≥ f ( x + 1 ) − f ( x ) , x ∈ Z \forall f(x)-f(x-1)\ge f(x+1)-f(x),x\in \Z f(x)f(x1)f(x+1)f(x),xZ,则称其为凸函数.画个图/想想可以发现这是函数围成了一个上凸壳.

对于定义在实数域上的函数 f ( x ) , ∀ x ∈ R , d > 0 , f ′ ( x ) ≥ f ′ ( x + d ) f(x),\forall x\in R,d>0,f'(x)\ge f'(x+d) f(x),xR,d>0,f(x)f(x+d).

类似的,我们把 ≥ \ge ≤ \le 互换一下即可得到凹函数的定义.

引理:

我们把从原点出发(不包含原点)的射线称为一条臂.

设一条臂的点数为 x x x,

当我们在这条臂上选择 ≤ min ⁡ ( x , ( k + 1 ) / 2 ) \le \min(x,(k+1)/2) min(x,(k+1)/2)个点,那么这些点必定在最远端,剩下的选择点必定在最近端.

证明: 设存在两个臂上相邻的点 A , B , d i s ( A , O ) > d i s ( B , O ) , l = d i s ( A , O ) − d i s ( B , O ) A,B,dis(A,O)>dis(B,O),l=dis(A,O)-dis(B,O) A,B,dis(A,O)>dis(B,O),l=dis(A,O)dis(B,O), A A A没选, B B B选了, B B B远端还有 t t t个点的话.

那么我们把 B − > A B->A B>A,则变化量为 l ( k − 1 − t ) − l t = l ( k − 1 − 2 t ) l(k-1-t)-lt=l(k-1-2t) l(k1t)lt=l(k12t),那么当单臂选择总点数 ≤ min ⁡ ( x , ( k + 1 ) / 2 ) \le \min(x,(k+1)/2) min(x,(k+1)/2),我们都应该选择远端.

否则,当总点数 > ( k + 1 ) / 2 >(k+1)/2 >(k+1)/2时,变化量就是负数,我们把它推得越近,答案越大.

先贴个代码再解释一下:

int n,k;
map<pi,int> s; int m;
vector<double> v[N]; 
double a[N],ans; int cnt;

int main() {
	qr(n); qr(k);
	for(int i=1,x,y,d;i<=n;i++) {
		qr(x); qr(y); if(!x&&!y) continue;
		d=gcd(abs(x),abs(y));
		pi t=mk(x/d,y/d);
		if(!s.count(t)) s[t]=++m;
		int id=s[t];
		v[id].push_back(sqrt(1.0*x*x+1.0*y*y));
	}
	a[++cnt]=0;
	for(int i=1;i<=m;i++) {
		sort(v[i].begin(),v[i].end(),greater<double>());
		int l=0,r=SZ(v[i])-1;
		double sr=0;
		while(l<=r) {
			double left =v[i][l]*(k-1-2*l)-sr*2;
			double right=v[i][r]*(k-1-2*l)-sr*2;
			if(left>=right) l++,a[++cnt]=left;
			else sr+=v[i][r--],a[++cnt]=right;
		}
	}
	sort(a+1,a+cnt+1,greater<double>());
	for(int i=1;i<=k;i++) ans+=a[i];
	printf("%.10lf\n",ans);
	return 0;
}

重要部分是:

while(l<=r) {
    double left =v[i][l]*(k-1-2*l)-sr*2;
    double right=v[i][r]*(k-1-2*l)-sr*2;
    if(left>=right) l++,a[++cnt]=left;
    else sr+=v[i][r--],a[++cnt]=right;
}

这样的话,单臂选择数 ≤ ( k + 1 ) / 2 \le (k+1)/2 (k+1)/2,则我们选择左端点(远端),否则选择右端点(近端).

在我们一直选择左边的时候, v [ i ] [ l ] , ( k − 1 − 2 l ) v[i][l],(k-1-2l) v[i][l],(k12l)在减小,所以贡献在减小( Δ f ( x ) \Delta f(x) Δf(x)减小).

当开始选择右边的时候, v [ i ] [ r ] 增 加 , ( k − 1 − 2 l ) 是 负 数 且 不 断 减 小 v[i][r]增加,(k-1-2l)是负数且不断减小 v[i][r],(k12l),所以贡献也在减小.

而且左边的贡献为正,右边的贡献为负.

所以这是一个凸函数.

重头戏:合并凸函数.

直接把所有 Δ f ( x ) \Delta f(x) Δf(x)放入一个数组,然后取最大的 k k k个值即可.

正确性:你取一个小的数,那么大的数一定取完了,也就是取了一个函数的合法值(对应着取了一个臂的一些点),所以正确.

E E E

给你一个强连通图,你需要找到这个图的有趣的点.

一个点 x x x是有趣的点,当且仅当它到所有点都只有一条简单路径(不含重复的点).

如果有趣的点 < n / 5 <n/5,那么输出-1.否则输出所有有趣的点.

判定一个点是有趣的点:

​ 构造一棵搜索树,如果有非树枝边连向非祖先点,则不有趣.

利用概率的性质,我们先随机找到一个有趣的点.

假设随机 T = 100 T=100 T=100次,且答案不为-1,

那么都找不到的概率为 ( 4 5 ) T ≈ 2 ∗ 1 0 − 10 (\dfrac 4 5)^T\approx 2*10^{-10} (54)T21010,所以大可不必担心找不到(只要你的随机函数够好).

找到以后,我们定义一个点被覆盖的次数 t [ x ] t[x] t[x]为它的子树内能到达其祖先的点的个数.

如果 t [ x ] > 1 t[x]>1 t[x]>1,那么显然这个点是不有趣的,因为有两条不等的路走到祖先(注意,他们的能走到的祖先不同也没有关系).

然后我们二次扫描------根据一个点的前驱是坏点,那么这个点也是坏点这一性质.(这里我们直接把前驱定为能到达的最早的祖先是最优的)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define lc (x<<1)
#define rc (x<<1|1)
#define gc getchar()//(p1==p2&&(p2=(p1=buf)+fread(buf,1,size,stdin),p1==p2)?EOF:*p1++)
#define mk make_pair
#define pi pair
#define pb push_back
#define IT iterator 
#define SZ(a) ((int)a.size())
#define all(a) a.begin(),a.end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
const int N=1e5+10,size=1<<20,mod=998244353;

//char buf[size],*p1=buf,*p2=buf;
template<class o> void qr(o &x) {
	char c=gc; x=0; int f=1;
	while(!isdigit(c)){if(c=='-')f=-1; c=gc;}
	while(isdigit(c)) x=x*10+c-'0',c=gc;
	x*=f;
}
template<class o> void qw(o x) {
	if(x/10) qw(x/10);
	putchar(x%10+'0');
}
template<class o> void pr1(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); putchar(' ');
}
template<class o> void pr2(o x) {
	if(x<0)x=-x,putchar('-');
	qw(x); puts("");
}

int n,m,vis[N],dep[N],pre[N],t[N]; 
bool bad[N];
vector<int> e[N];

void clear() {
	for(int i=1;i<=n;i++)
		vis[i]=0,e[i].clear();
}

void out() {puts("-1\n"); clear();}
//找到一个有趣的点 
bool dfs(int x) {
	vis[x]=1;
	for(int y:e[x]) 
		if( vis[y]==2 || (!vis[y]&&!dfs(y)) )
			return 0;
	return vis[x]=2,1;
}

bool check(int x) {
	memset(vis+1,0,sizeof(int[n]));
	return dfs(x);
}

int Rand(int x) {
	static mt19937 rnd(time(0));
	return rnd()%x+1;
}

int Find() {
	int T=100;
	while(T--) {
		int x=Rand(n);
		if(check(x)) return x;
	}
	return -1;
}

//构造搜索树 
int dfs1(int x) {
	pre[x]=x; vis[x]=1; t[x]=0;
	for(int y:e[x]) 
		if(!vis[y]) {
			dep[y]=dep[x]+1;
			t[x]+=dfs1(y);
			if(dep[pre[x]]>dep[pre[y]]) pre[x]=pre[y];
		}
		else {
			t[x]++;
			t[y]--;//差分 
			if(dep[pre[x]]>dep[y]) pre[x]=y;
		}
	bad[x]=(t[x]>1);
	return t[x];
}
//二次扫描 
void dfs2(int x) {
	vis[x]=1;
	if(bad[pre[x]]) bad[x]=1;
	for(int y:e[x])	
		if(!vis[y]) dfs2(y);
}

int main() {
	int _;qr(_); while(_--) {
		qr(n); qr(m);
		for(int i=1,x,y;i<=m;i++) 
			qr(x),qr(y),e[x].pb(y);
		int x=Find();
		if(x==-1) {out(); continue;}
		memset(vis+1,0,sizeof(int[n]));
		dep[x]=1; dfs1(x);
		memset(vis+1,0,sizeof(int[n]));
		dfs2(x); int cnt=0;
		for(int i=1;i<=n;i++) cnt+=!bad[i];
		if(cnt*5>=n) {for(int i=1;i<=n;i++) if(!bad[i]) pr1(i); puts("\n"); clear();}
		else out();
	}
	return 0;
}


你可能感兴趣的:(比赛)