[NOIP2018模拟10.15]比赛报告

闲扯

昨晚又颓到好晚,Yali的降智光环感觉持续至今...

题面好评 T1T3都玩过 逃)

T1没看多久就开始写二分+并查集 然后T3看着眼熟想了一个多小时...结果啥都没想出来

赶紧看T2发现还是没什么思路,码个暴力回来看T1,发现了两个致命又SB的错误,倒数15分钟前终于改回来,刺激

结果80+35+0 T1还是挂分了,检查时发现还是一个思博错误,没有判上下相连与左右相连情况,感谢出题人良心数据

T2调了好久结果爆栈RE,不想改了。T3听完晚上的讲评后才茅塞顿开,太菜了

T1 刺客信条 AC

分析

我的做法很naiive,直接距离,将每个人看成圆心画一个二分距离的圆,两个圆有交就连在一起,最后判断Ezio能不能过去.

这里的判读大佬们都是将4面墙看成4个点处理,我最SB,每个点开四个bool变量记录,合并时暴力合并bool值

std求了个最小生成树,比较神奇

然后加了一些优化,预处理点对距离,特判之类的一开始跑了个rank4

代码

/*
  code by RyeCatcher
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <utility>
#include <cmath>
#include <queue>
#include <vector>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <iostream>
#define DEBUG freopen("dat.in","r",stdin);freopen("wa.out","w",stdout);
#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}
#define ri register int
#define ll long long
#define ull unsigned long long
#define SIZE 1<<22
using std::sqrt;
using std::min;
using std::max;
using std::priority_queue;
using std::queue;
using std::vector;
using std::pair;
using namespace __gnu_pbds;
inline char gc(){
    static char buf[SIZE],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,SIZE,stdin),p1==p2)?EOF:*p1++;
}
#define gc getchar
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while((c=gc())>'9'||c<'0')ne=c=='-';x=c-48;
    while((c=gc())>='0'&&c<='9')x=(x*10)+c-48;x=ne?-x:x;return ;
}
const int maxn=3005;
const int inf=0x7fffffff;
const long double eps=1e-7;
struct Pt{
    long double x,y;
}pt[maxn];
long double X,Y;
int fa[maxn];
struct Flag{
    bool f1,f2,f3,f4;
    inline void clear(){f1=f2=f3=f4=0;}
}ff[maxn];
inline void merge(int x,int y){
    if(ff[x].f1|ff[y].f1)ff[x].f1=ff[y].f1=1;
    if(ff[x].f2|ff[y].f2)ff[x].f2=ff[y].f2=1;
    if(ff[x].f3|ff[y].f3)ff[x].f3=ff[y].f3=1;
    if(ff[x].f4|ff[y].f4)ff[x].f4=ff[y].f4=1;
    return ;
}
int get(int x){
    merge(x,fa[x]);
    if(fa[x]!=x){
        fa[x]=get(fa[x]);
    }
    return fa[x];
}
bool vis[maxn];
long double fafa[maxn][maxn];
int n;
bool check(long double Dis){
    for(ri i=1;i<=n;i++){
        vis[i]=0;
        fa[i]=i;
		ff[i].clear();
    }
    long double x,y;
    for(ri i=1;i<=n;i++){
    	int cnt=0;
        x=pt[i].x,y=pt[i].y;
        if(Dis-x>1e-11)ff[i].f1=1,cnt++;
        if(Dis-y>1e-11)ff[i].f2=1,cnt++;
        if(Dis-(X-x)>1e-11)ff[i].f3=1,cnt++;
        if(Dis-(Y-y)>1e-11)ff[i].f4=1,cnt++;
        //if(cnt>=2)return 1;
        if(ff[i].f1&&ff[i].f2)return 1;
        if(ff[i].f3&&ff[i].f4)return 1;
        
    }
    int fx,fy;
    for(ri i=1;i<=n;i++){
        x=pt[i].x,y=pt[i].y;
        for(ri j=i+1;j<=n;j++){
            long double p=fafa[i][j];
            if(Dis*2-p>1e-11){
                fx=get(i),fy=get(j);
                fa[fx]=fy;
                merge(fx,fy);
            }
        }
    }
    for(ri i=1;i<=n;i++){
        fx=get(i);
        if(vis[fx])continue;
        vis[fx]=1;
        int tmp=ff[fx].f1+ff[fx].f2+ff[fx].f3+ff[fx].f4;
        if(ff[fx].f1&&ff[fx].f2)return 1;
        if(ff[fx].f3&&ff[fx].f4)return 1;
        if(ff[fx].f2&&ff[fx].f4)return 1;
        if(ff[fx].f1&&ff[fx].f3)return 1;
    }
    return 0;
}
int main(){
	long double x,y;
	//freopen("AC.in","r",stdin);
	//freopen("AC.out","w",stdout);
    read(X),read(Y),read(n);
    for(ri i=1;i<=n;i++){
        read(pt[i].x),read(pt[i].y);
    }
    for(ri i=1;i<=n;i++){
        x=pt[i].x,y=pt[i].y;
        for(ri j=i+1;j<=n;j++){
            fafa[i][j]=sqrtl((x-pt[j].x)*(x-pt[j].x)+(y-pt[j].y)*(y-pt[j].y));
        }
    }
    long double mid,L=0,R=sqrtl(X*X+Y*Y+233);
    while(R-L>eps){
        mid=(L+R)/2;
        if(check(mid))R=mid;
        else L=mid;
        //printf("%.4lf %.4lf\n",L,R);
    }
    printf("%.2Lf\n",R);
    return 0;
}

T2 黑暗之魂 darksoul

分析

先不考虑自环和重边,最终的图像肯定是一个环,环上若干点,点可能向外扩展成一棵树

假如最终答案在一颗树中,我们就要求出树上相距最远的两点,用树形DP即可,

\(g[x]\)表示向下在以x为根的子树中最远能扩展到哪里,\(o[x]\)表示次远值

g[x]=max(g[x],g[v]+dis(x,v)),o[x]就不赘述了

然后以x为LCA的两点最远值f[x]=g[x]+o[x],然后\(max_{x \in T}(f[x])\)就是树T的贡献

但是如果答案的路径经过了环上的边呢,对于环上路径\((x,y)\). (x,y都是环上的点)

它的贡献为\(g[x]+g[y]+dis(x,y)\)

\(g\)值我们是已经求出来的,但是\(dis\)怎么求?我们化环为链,钦定起点\(st\),用前缀和数组\(pre[x]\)表示\(dis(st,x)\)

那么\(dis(x,y)\)就是\(pre[x]-pre[y]\)(设\(x\)\(y\)之后),注意我们要倍长这条链

这样贡献就变成了\(g[x]+pre[x]+g[y]-pre[y]\)

然而需要注意的是如果\(pre[x]-pre[y]\)大于环周长的一半是不合法的,为啥?因为他总是选择最短路走,既然这段大于周长一半,反过来走肯定更短

暴力的做法就是N方环上每一对点枚举一遍,考虑高级一点的做法,发现化环为链后处理的\(pre\)数组是单调递增的,

于是维护一个\(g[y]-pre[y]\)值递减的滑动窗口(因为当前点为\(x\),\(g[x]+pre[x]\)是固定的),一旦队头\(p\)到当前点距离,即\(pre[x]-pre[p]\)大于两倍周长就弹出队头

然后大佬们都是用拓扑排序搞,我只会naiive的深搜,然后交上去最后两个点爆栈了

同时还发现我没有考虑答案是在一棵树中的情况,只能说这数据水了...

代码

/*
  code by RyeCatcher
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <utility>
#include <queue>
#include <vector>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <iostream>
#define DEBUG freopen("dat.in","r",stdin);freopen("wa.out","w",stdout);
#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}
#define ri register int
#define ll long long
#define ull unsigned long long
#define SIZE 1<<22
using std::min;
using std::max;
using std::priority_queue;
using std::deque;
using std::vector;
using std::pair;
using namespace __gnu_pbds;
inline char gc(){
    static char buf[SIZE],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,SIZE,stdin),p1==p2)?EOF:*p1++;
}
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while((c=gc())>'9'||c<'0')ne=c=='-';x=c-48;
    while((c=gc())>='0'&&c<='9')x=(x<<3)+(x<<1)+c-48;x=ne?-x:x;return ;
}
const int maxn=2000005;
const int inf=0x7fffffff;
struct Edge{
	int ne,to;
	ll dis;
}edge[maxn<<1];
int h[maxn];ll num_edge=1;
inline void add_edge(int f,int to,ll c){
	edge[++num_edge].ne=h[f];
	edge[num_edge].to=to;
	edge[num_edge].dis=c;
	h[f]=num_edge;
}
int n;
int fa[maxn],dep[maxn];
bool vis[maxn],flag_1;
int fo1,fo2;
void pre_dfs(int now,int fa){
    int v;
    vis[now]=1;
    for(ri i=h[now];i;i=edge[i].ne){
        v=edge[i].to;
        if(v==fa)continue;
        if(vis[v]&&dep[v]==dep[now]+1){
        	//puts("11");
			for(ri k=h[now];k;k=edge[k].ne){
				//printf("%d\n",edge[k].to);
				if(edge[k].to==v){
					if(!fo1)fo1=k;
					else fo2=k;
				}
			}
			flag_1=1;return ;
		}
		else if(vis[v])continue;
        dep[v]=dep[now]+1;
        pre_dfs(v,now);
    }
    return;
}
namespace Tree{
    ll Tmp=-1,ans=-1;
    int rt=1;
    void dfs_1(int now,int fa,ll dis){
        int v;
        if(Tmp<dis){
            rt=now,Tmp=dis;
        }
        for(ri i=h[now];i;i=edge[i].ne){
            v=edge[i].to;
            if(i==fo1||i==fo2)continue;
            if(v==fa||v==now)continue;
            dfs_1(v,now,dis+edge[i].dis);
        }
        return ;
    }
    void dfs_2(int now,int fa,ll dis){
        int v;
        ans=max(ans,dis);
        for(ri i=h[now];i;i=edge[i].ne){
            v=edge[i].to;
            if(i==fo1||i==fo2)continue;
            if(v==fa||v==now)continue;
            dfs_2(v,now,dis+edge[i].dis);
        }
        return ;
    }
    void main(){
    	if(edge[fo1].dis<edge[fo2].dis){
    		fo1=fo2^1;
		}
		else{
			fo2=fo1^1;
		}
        dfs_1(1,0,0);
        Tmp=-1;
        dfs_2(rt,0,0);
        printf("%lld\n",ans+1);
        return ;
    }
}
int sta[maxn],cnt=0,st,ed,len=0;
bool on_cyc[maxn];
void find_cyc(int now){
	int v;
	for(ri i=h[now];i;i=edge[i].ne){
		v=edge[i].to;
		if(v==fa[now])continue;
		if(dep[v]&&dep[v]<dep[now]){
			st=now,ed=v;
			int x=now;
			while(x!=v)len++,on_cyc[x]=1,sta[++cnt]=x,x=fa[x];
			len++,on_cyc[v]=1,sta[++cnt]=v;
			continue;
		}
		else if(dep[v])continue;
		fa[v]=now;
		dep[v]=dep[now]+1;
		find_cyc(v);
	}
	return ;
}
int rt;
ll dm[maxn];
ll tmp=-1,cc=0;
ll dp[maxn];
void dfs_1(int now,int fa){
	int v;
	for(ri i=h[now];i;i=edge[i].ne){
		v=edge[i].to;
		if(on_cyc[v]||v==fa)continue;
		dfs_1(v,now);
		dp[now]=max(dp[now],dp[v]+edge[i].dis);
	}
	return ;
}
ll pre[maxn];//dist from st
int ff[maxn],ne[maxn],tot=0;
void dfs_on_cyc(int now,int fa){
	int v;
	if(tot==len*2-1)return ;
	for(ri i=h[now];i;i=edge[i].ne){
		v=edge[i].to;
		if(v!=ne[now])continue;
		pre[tot+1]=pre[tot]+edge[i].dis;
		ff[++tot]=v;
		if(tot==len+1)cc=pre[tot];
		dfs_on_cyc(v,now);
	}
	return ;
}
deque <int> q;
ll arr[maxn];
int main(){	
	bool flag=0;
	int x,y;ll z;
	FO(darksoul);
	//freopen("darksoul19.in","r",stdin);
	read(n);
	//puts("sss");
	//system("PAUSE");
	for(ri i=1;i<=n;i++){
		read(x),read(y),read(z);
		add_edge(x,y,z);
		add_edge(y,x,z);
		if(x==y)flag=1;
	}
	dep[1]=1;
	pre_dfs(1,0);
	if(flag||flag_1){
		//puts("ss");
		Tree::main();return 0;
	}
	//memset(vis,0,sizeof(vis));
	memset(dep,0,sizeof(dep));
	fa[1]=0,dep[1]=1;
	find_cyc(1);
	for(ri i=1;i<=cnt;i++){
		rt=sta[i];
		if(i!=cnt)ne[sta[i]]=sta[i+1];
		else ne[sta[i]]=sta[1];
		tmp=-1;
		dfs_1(rt,0);
		dm[sta[i]]=dp[rt];
	}
	tot=1,ff[1]=sta[1];
	dfs_on_cyc(st,0);
	for(ri i=1;i<=len*2-1;i++){		
		arr[i]=dm[ff[i]]-pre[i];
	}
	ll ans=-1;
	for(ri k=1;k<=len*2-1;k++){				
		if(q.empty())q.push_back(k);
		else{	
			while(q.size()&&pre[k]-pre[q.front()]>cc/2)q.pop_front();		
			ans=max(ans,dm[ff[k]]+pre[k]+arr[q.front()]);
			while(q.size()&&arr[q.back()]<=arr[k])q.pop_back();
			q.push_back(k);
		}
	}
	printf("%lld\n",ans+1);
	return 0;
}

T3 传送门 portal

分析

巧妙的树形DP

假设当前我们正在\(x\)点,\(y\)\(x\)的一个儿子,\(f[x]\)表示以\(x\)为根的子树的最优答案,

ialsk4.png

那么我们考虑假如传送门在\(y\),那么\(y\)的贡献就是\(f[y]+c*2\),因为\(x,y\)还得靠你自己走

假如一个传送门在\(x\),那么\(y\)的贡献为\(sum_e(y) \times 2-g(y)+c\),\(sum_e(y)\)表示\(y\)的子树中边权之和,\(g(y)表示\)\(y\)的子树中的最长链长度.

这时候有人会问一个问题,你这样为什么不每次走到底再传送到x然后再经过\(c\),但是这样的话要经过多次\(c\),为什么不干脆将传送门设在\(y\),这样的贡献肯定不会比你那样走更多,所以我们这时候要选择最长链跳,其余的都靠步行的方式计算贡献

综上\(f[x]= \sum min(f[y]+c \times 2,sum_e(y) \times 2-g(y)+c)\)

由于我们能够安排儿子的\(dfs\)顺序,可知儿子之间是不会互相影响的

代码

/*
  code by RyeCatcher
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cctype>
#include <utility>
#include <queue>
#include <vector>
#include <ext/pb_ds/hash_policy.hpp>
#include <ext/pb_ds/assoc_container.hpp>
#include <iostream>
#define DEBUG freopen("dat.in","r",stdin);freopen("wa.out","w",stdout);
#define FO(x) {freopen(#x".in","r",stdin);freopen(#x".out","w",stdout);}
#define ri register int
#define ll long long
#define ull unsigned long long
#define SIZE 1<<22
using std::min;
using std::max;
using std::priority_queue;
using std::queue;
using std::vector;
using std::pair;
using namespace __gnu_pbds;
inline char gc(){
    static char buf[SIZE],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,SIZE,stdin),p1==p2)?EOF:*p1++;
}
#define gc getchar
template <class T>inline void read(T &x){
    x=0;int ne=0;char c;
    while((c=gc())>'9'||c<'0')ne=c=='-';x=c-48;
    while((c=gc())>='0'&&c<='9')x=(x<<3)+(x<<1)+c-48;x=ne?-x:x;return ;
}
const int maxn=1000005;
const int inf=0x7fffffff;
int n;
struct Edge{
	int ne,to;
	ll dis;
}edge[maxn<<1];
int h[maxn],num_edge=1;
inline void add_edge(int f,int to,ll c){
	edge[++num_edge].ne=h[f];
	edge[num_edge].to=to;
	edge[num_edge].dis=c;
	h[f]=num_edge;
}
ll g[maxn],s[maxn],f[maxn];
void dfs(int now,int fa){
	int v;ll c;
	for(ri i=h[now];i;i=edge[i].ne){
		v=edge[i].to;
		if(v==fa)continue;
		dfs(v,now);
		c=edge[i].dis;
		s[now]=s[now]+s[v]+c;
		g[now]=max(g[now],g[v]+c);
		f[now]+=min(s[v]*2-g[v]+c,f[v]+c*2);
	}
	return ;
}
int main(){
	int x,y,z;
	FO(portal);
	read(n);
	for(ri i=1;i<n;i++){
		read(x),read(y),read(z);
		add_edge(x,y,z);
		add_edge(y,x,z);
	}
	dfs(1,0);
	printf("%lld\n",f[1]);
	return 0;
}


posted @ 2018-10-15 23:17  Rye_Catcher  阅读(184)  评论(1编辑  收藏  举报