UOJ Round #4 题解

A

f [ i ] [ j ] f[i][j] f[i][j] 表示 a = i , b = j a=i,b=j a=i,b=j 时必胜还是必败,然后做个 d p dp dp 即可。

由于只有当 j = 1 j=1 j=1 时, i i i 可能很大,但容易发现当 i > n i>\sqrt n i>n 时,两个人都只能操作 a a a,因为一操作 b b b 就超过 n n n 了,所以判断一下 n − a n-a na 的奇偶性即可。

j > 1 j>1 j>1 时, i i i 的枚举范围很小,直接暴力枚举转移即可。

如果 i j > n i^j>n ij>n,那么 f [ i ] [ j ] f[i][j] f[i][j] 就是必胜态。由于bool数组的初始状态为false,所以方便一点在代码中定义false为必胜态。

代码如下:

#include <cstdio>
#include <cmath>
#define ll long long

int n,m;
bool f[40010][40],v[40010][40];//v[i][j]记录i^j是否小于等于n,是则为true
ll ksm(int x,int y)
{
	ll re=1;
	while(y)
	{
		if(y&1)re=re*x;
		x=x*x;y>>=1;
	}
	return re;
}
void dp()
{
	for(int i=2;i<=40000;i++)
	for(ll j=1,tot=i;tot<=n&&j<=30;j++,tot*=i)v[i][j]=true;
	f[(int)sqrt(n)+1][1]=(n-(int)sqrt(n))%2;
	for(int j=30;j>=1;j--)
	for(int i=sqrt(n);i>=2;i--)
	f[i][j]=(v[i][j]&(!f[i+1][j])&(!f[i][j+1]));
}

int main()
{
	scanf("%d %d",&n,&m); dp();
	while(m--)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		if(ksm(x,y+1)>n&&ksm(x+1,y)>n)printf("No\n");//假如无论怎么走都大于n,则必败
		else if(y==1&&x>sqrt(n))printf((n-x)%2==1?"Yes\n":"No\n");
		else if(!f[x][y])printf("Yes\n");
		else printf("No\n");
	}
}

B

考虑一次看每一个数列第 k 3 \dfrac k 3 3k 小的位置,该位置最小的那个数列的前 k 3 \dfrac k 3 3k 个数,一定都是前 k k k 小的,于是可以使 k − = k 3 k-=\dfrac k 3 k=3k,然后这个数列下次就从 k 3 + 1 \dfrac k 3+1 3k+1 开始算。

这样每次减少 1 3 \dfrac 1 3 31,大约 3 log ⁡ 3 k 3\log_3k 3log3k 次查询即可解决。

代码如下:

#include <cstdio>
#include <cstring>
#include "kth.h"
#include <algorithm>
using namespace std;

int get_a(int p);
int get_b(int p);
int get_c(int p);
int get(int id,int x){
	if(id==0)return get_a(x-1);
	if(id==1)return get_b(x-1);
	if(id==2)return get_c(x-1);
}
struct par{int id,x,y;};
bool cmp(par x,par y){return x.x<y.x;}
int query_kth(int na,int nb,int nc,int k){
	static int n[3],last[3],ans;
	static vector<par> d;
	n[0]=na;n[1]=nb;n[2]=nc;
	for(int i=0;i<3;i++)last[i]=0;
	//事实上,由于当询问超出n时会返回2^31-1,所以其实不用判那么多边界。
	while(k){
		d.clear();
		int tot=0;
		for(int i=0;i<3;i++)if(last[i]<n[i])tot++;
		tot=max(k/tot,1);
		for(int i=0;i<3;i++)if(last[i]<n[i]){
			int next=min(last[i]+tot,n[i]);
			par p=(par){i,get(i,next),next-last[i]};
			d.push_back(p);
		}
		sort(d.begin(),d.end(),cmp);
		par p=*d.begin();
		k-=p.y;
		ans=p.x;
		last[p.id]+=p.y;
	}
	return ans;
}
//上面是AC代码,下面是用来本地测试的,本地测试时需要注释掉上面的kth.h头文件
#define maxn 100010
int a[maxn],b[maxn],c[maxn];
int get_a(int p){return a[p];}
int get_b(int p){return b[p];}
int get_c(int p){return c[p];}

int main()
{
	int na,nb,nc,k;
	scanf("%d %d %d %d",&na,&nb,&nc,&k);
	for(int i=0;i<na;i++)scanf("%d",&a[i]);
	for(int i=0;i<nb;i++)scanf("%d",&b[i]);
	for(int i=0;i<nc;i++)scanf("%d",&c[i]);
	printf("%d",query_kth(na,nb,nc,k));
}

当然也有一些很骚的写法,像这样:

#include "kth.h"
#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 100010

int na,nb,nc,k,ans,nowa=1,nowb=1,nowc=1;
int a[maxn],b[maxn],c[maxn];
struct par{int x,y;};
par s[10]; int t;
bool cmp(par x,par y){return x.x<y.x;}
inline char cn()
{
	static char buf[1000010],*p1=buf,*p2=buf;
	return p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++;
}
void read(int &x)
{
	x=0;int f1=1;char ch=cn();
	while(ch<'0'||ch>'9'){if(ch=='-')f1=-1;ch=cn();}
	while(ch>='0'&&ch<='9')x=x*10+(ch-'0'),ch=cn();
	x*=f1;
}
int next()
{
	t=0;
	if(nowa<=na)s[++t]=(par){a[nowa],1};
	if(nowb<=nb)s[++t]=(par){b[nowb],2};
	if(nowc<=nc)s[++t]=(par){c[nowc],3};
	sort(s+1,s+t+1,cmp);
	if(s[1].y==1)return a[nowa++];
	if(s[1].y==2)return b[nowb++];
	if(s[1].y==3)return c[nowc++];
}
void ungetc(int x){ungetc(x,stdin);}
int init()
{
	read(na);read(nb);read(nc);read(k);
	for(int i=1;i<=na;i++)read(a[i]);
	for(int i=1;i<=nb;i++)read(b[i]);
	for(int i=1;i<=nc;i++)read(c[i]);
	ungetc(10);ungetc('1');ungetc(10);ungetc(10);ungetc(10);
    ungetc('1');ungetc(' ');ungetc('1');ungetc(' ');ungetc('0');ungetc(' ');ungetc('0');
	while(k--)ans=next();
	return 1;
}
int wulala=init();
int query_kth(int n_a,int n_b,int n_c,int k){return ans;}

即提前将输入读进来,然后直接用手写堆求解,这样一次询问都不用……

然后要注意再往输入流里塞一个假数据骗骗评测机,别让他输入RE了或什么的。

不过其实不推崇这个做法,毕竟就没有思维难度和收获了……

C

旧题解传送门

以前写代码又臭又长,还AC不了,居然这篇题解当时还能不知廉耻的占据到百度搜索时的第一位,真是惭愧,现在补了这题先谢个罪……

思路大体是没变的,有些地方有变动,再重新讲一次。

先考虑暴力,前 k k k 大肯定要用堆来求,在堆里存现在的所有路径的末尾,每次取出最小的然后将所有后继路径都加进去,这样是 O ( n k log ⁡ ( n k ) ) O(nk\log (nk)) O(nklog(nk)) 的。

考虑优化,简单讨论一下可以将 a i , b i , c i a_i,b_i,c_i ai,bi,ci 这三个点之间的路径拆成两条链,找到一条链上的 w w w 最小的点,当有一条路径从 i i i 往外走时,只需要考虑走到这两条链上的最小点上即可。

但是这不是说路径上其他点不需要考虑了,显然也是要的,假设 x x x 所在的链为 ( u , v ) (u,v) (u,v),那么从 x x x 往外走时,不单单加入 x x x 到上面说的两条链上的最小点这两条路径,假设 x x x 这条路径的上一个点为 p p p,你还需要加入 c c c ( u , x ) , ( v , x ) (u,x),(v,x) (u,x),(v,x) 这两条链的最小点这两条路径,这样就可以将所有路径考虑上了。(注意,这里 ( u , x ) , ( v , x ) (u,x),(v,x) (u,x),(v,x) 两条链不包括 x x x 本身)。

关于如何将 a i , b i , c i a_i,b_i,c_i ai,bi,ci 三点之间的路径拆成两条链:为了方便,设这三个点为 x , y , z x,y,z x,y,z,那么其实主要只有这两种情况:
在这里插入图片描述
注意到,交换 x , y , z x,y,z x,y,z 的顺序,一定存在一种顺序可以使 lca ( x , z ) = lca ( y , z ) \text{lca}(x,z)=\text{lca}(y,z) lca(x,z)=lca(y,z),此时三点关系如上图, z z z 可能是 x , y x,y x,y 的祖先,但是不是其实都无所谓。

两图的不同在于 x , y x,y x,y 之间的关系,若 y y y x x x 的祖先,那么其实只需要分解成 ( x , z ) (x,z) (x,z) 这一条链即可,否则分解为 ( x , y ) , ( f a [ lca ( x , y ) ] , z ) (x,y),(fa[\text{lca}(x,y)],z) (x,y),(fa[lca(x,y)],z)

以及一些卡空间上的细节:

  1. 倍增空间太大,要用树链剖分(这个官方题解就有说)
  2. 线段树我用的zkw,只需要大约 1 0 6 10^6 106 个int
  3. 存边用邻接表比用vector好,既快不少又能省大概 7 M 7M 7M 空间
  4. 有一些显然不优的路径就不放到堆里面了
  5. 原来有一些数组用一次就不用了——但事实上后面你可以循环利用,虽然变量名会有些别扭
  6. 这题时间充裕,可以考虑在一些地方时间换空间,但这份代码里实际上没有使用。

代码如下:

#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
#define maxn 500010

#define cn getchar
void read(int &x){
	x=0;int f1=1;char ch=cn();
	while(ch<'0'||ch>'9'){if(ch=='-')f1=-1;ch=cn();}
	while(ch>='0'&&ch<='9')x=x*10+(ch-'0'),ch=cn(); x*=f1;
}
void write(int x){
	static char op[110];
	static int t;t=0;
	if(!x)putchar('0');
	while(x)op[++t]=x%10+'0',x/=10;
	while(t)putchar(op[t--]);
}
int n,k,w[maxn],wmax=0;
struct edge{int y,next;}e[maxn];
int first[maxn],len=0;
void buildroad(int x,int y){e[++len]=(edge){y,first[x]};first[x]=len;}
int fa[maxn],dep[maxn],sz[maxn],mson[maxn];
void dfs1(int x){
	sz[x]=1;
	for(int i=first[x];i;i=e[i].next){
		int y=e[i].y;dep[y]=dep[x]+1;dfs1(y);
		if(sz[y]>sz[mson[x]])mson[x]=y;
		sz[x]+=sz[y];
	}
}
int id[maxn],idtot=0,top[maxn],mi[maxn];
void dfs2(int x,int tp){
	top[x]=tp;id[x]=++idtot;sz[idtot]=x;
	if(tp==x)mi[x]=x;
	else mi[x]=(w[x]<w[mi[fa[x]]]?x:mi[fa[x]]);
	
	if(mson[x])dfs2(mson[x],tp);
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=mson[x])dfs2(e[i].y,e[i].y);
}
int lca(int x,int y){
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]])swap(x,y);
		y=fa[top[y]];
	}
	return dep[x]>dep[y]?y:x;
}
struct zkw{
	int mi[1048580],m;
	void buildtree(int N){
		for(m=1;m+2<=N;m<<=1);
		for(int i=m+1;i<=m+n;i++)mi[i]=sz[i-m];
		for(int i=m;i>=1;i--)mi[i]=(w[mi[i<<1]]<w[mi[i<<1|1]]?mi[i<<1]:mi[i<<1|1]);
	}
	int ask(int x,int y){
		int re=0;
		for(x=x+m-1,y=y+m+1;x^y^1;x>>=1,y>>=1){
			if(~x&1)re=(w[re]>w[mi[x^1]]?mi[x^1]:re);
			if( y&1)re=(w[re]>w[mi[y^1]]?mi[y^1]:re);
		}
		return re;
	}
}tr;
struct node{int mi,new_x;};
node get_min(int x,int y){
	if(x==y)return (node){0,0};
	int px=x,last,new_x,re=0;
	while(top[x]!=top[y]){
		if(dep[top[x]]>dep[top[y]]){
			if(x==px){x=new_x=fa[x];continue;}
			swap(x,y);
		}
		if(w[mi[y]]<w[re])re=mi[y];
		last=top[y];y=fa[last];
	}
	if(dep[x]>dep[y]){
		if(x==px)x=new_x=fa[x];
		swap(x,y);
	}
	if(x==px){
		if(x==y){new_x=last;return (node){re,new_x};}
		else x=new_x=mson[x];
	}
	last=tr.ask(id[x],id[y]);
	if(w[last]<w[re])re=last;
	return (node){re,new_x};
}
struct chain{int x,y,mi;}chain1[maxn],chain2[maxn];
chain make_chain(int x,int y,bool type){
	node p=get_min(x,y);
	if(type&&w[p.mi]>w[x])p.mi=x;
	if(type)p.new_x=x;
	return (chain){p.new_x,y,p.mi};
}
struct par{
	int x,z;chain be;
	bool operator <(const par &B)const{return z>B.z;}
};
priority_queue<par> q;
void qadd(par x){if(n<k||x.z<wmax)q.push(x);}

int main()
{
	read(n);read(k);w[0]=1e9;
	for(int i=1;i<=n;i++)read(w[i]),wmax=max(wmax,w[i]);
	for(int i=2;i<=n;i++)read(fa[i]),buildroad(fa[i],i);
	dfs1(1);dfs2(1,1);tr.buildtree(n);
	for(int i=1,x,y,z,xy,yz,xz;i<=n;i++){
		read(x);read(y);read(z);
		xy=lca(x,y);yz=lca(y,z);xz=lca(x,z);
		if(xy==xz)swap(x,z),swap(xy,yz);
		if(xy==yz)swap(y,z),swap(xy,xz);
		if(xy==x||xy==y)chain1[i]=make_chain(xy==x?y:x,z,1);
		else{
			chain1[i]=make_chain(x,y,1);
			chain2[i]=make_chain(xy,z,0);
		}
	}
	for(int i=1;i<=n;i++)q.push((par){i,w[i],(chain){0,0,0}});
	for(int nowk=1;nowk<=k;nowk++){
		par x=q.top();q.pop();
		write(x.z);puts("");
		
		if(x.be.x!=0){
			node p;
			if(x.be.x!=x.x){
				p=get_min(x.x,x.be.x);
				qadd((par){p.mi,x.z-w[x.x]+w[p.mi],(chain){p.new_x,x.be.x,p.mi}});
			}
			if(x.be.y!=x.x){
				p=get_min(x.x,x.be.y);
				qadd((par){p.mi,x.z-w[x.x]+w[p.mi],(chain){p.new_x,x.be.y,p.mi}});
			}
		}
		qadd((par){chain1[x.x].mi,x.z+w[chain1[x.x].mi],chain1[x.x]});
		if(chain2[x.x].x!=0)qadd((par){chain2[x.x].mi,x.z+w[chain2[x.x].mi],chain2[x.x]});
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值