UOJ 347(洛谷4220) 【WC2018】通道——随机化

题目:http://uoj.ac/problem/347

   https://www.luogu.org/problemnew/show/P4220

先写了暴力分的44分。那个两棵树、其中一棵是编号连续的链、边权都是1的点好像可以线段树合并,但没写。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
ll rdn()
{
  ll ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return fx?ret:-ret;
}
ll Mx(ll a,ll b){return a>b?a:b;}
const int N=1e5+5,M=3005,K=20;
int n,hd[3][N],xnt,to[3][N<<1],nxt[3][N<<1];
ll w[3][N<<1],dis[N],ans,ds2[M][M]; bool flag;
void add(int t,int x,int y,ll z){to[t][++xnt]=y;nxt[t][xnt]=hd[t][x];hd[t][x]=xnt;w[t][xnt]=z;}
void chk(int cr,int fa)
{
  for(int i=hd[0][cr],v;i;i=nxt[0][i])
    if((v=to[0][i])!=fa)
      {
    bool fg=0;ll tw;
    for(int j=hd[1][cr];j;j=nxt[1][j])
      if(to[1][j]==v){fg=1;tw=w[1][j];break;}
    if(!fg||tw!=w[0][i]){flag=1;return;}
    for(int j=hd[2][cr];j;j=nxt[2][j])
      if(to[2][j]==v){fg=1;tw=w[2][j];break;}
    if(!fg||tw!=w[0][i]){flag=1;return;}
    chk(v,cr);if(flag)return;
      }
}
void dfs1(int cr,int fa)
{
  ll mx=0,mx2=0;
  for(int i=hd[0][cr],v;i;i=nxt[0][i])
    if((v=to[0][i])!=fa)
      {
    dfs1(v,cr);ll tp=dis[v]+w[0][i];
    if(tp>mx)mx2=mx,mx=tp;
    else if(tp>mx2)mx2=tp;
      }
  ans=Mx(ans,mx+mx2);dis[cr]=mx;
}
void dfs2(int cr,int fa,ll lj,int t,int rt)
{
  ds2[rt][cr]+=lj;
  for(int i=hd[t][cr],v;i;i=nxt[t][i])
    if((v=to[t][i])!=fa)dfs2(v,cr,lj+w[t][i],t,rt);
}
void solve2()
{
  for(int t=0;t<3;t++)
    for(int i=1;i<=n;i++)dfs2(i,0,0,t,i);
  for(int i=1;i<=n;i++)
    for(int j=i+1;j<=n;j++)ans=Mx(ans,ds2[i][j]);
  printf("%lld\n",ans);
}
int main()
{
  n=rdn();
  for(int t=0;t<3;t++)
    {
      ll z;xnt=0;
      for(int i=1,u,v;i<n;i++)
    u=rdn(),v=rdn(),z=rdn(),add(t,u,v,z),add(t,v,u,z);
    }
  if(n<=3000){solve2();return 0;}
  chk(1,0);if(!flag){dfs1(1,0);printf("%lld\n",ans*3);return 0;}
  return 0;
}
View Code

正解要边分治和虚树,现在还不会。所以用了随机化。

卡时间可以调用 clock() ,返回的是 CPU 周期,CLOCKS_PER_SEC 返回的是一秒运行了几个 CPU 周期,所以 clock() / CLOCKS_PER_SEC 可以算出运行了几秒。

随机一个根,暴力算每个点到这个根的距离(3个距离求和),把距离当前根最远的那个点设为新的根,同时更新答案。做几次之后就再随机一个根。这个间隙可以小一些,10或者8之类的。

可以打 vis[ ] 标记,作过根的点就不再作根了。

还是通不过 UOJ 的 hack 数据。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<ctime>
#define ll long long
#define db double
using namespace std;
ll rdn()
{
  ll ret=0;bool fx=1;char ch=getchar();
  while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return fx?ret:-ret;
}
ll Mx(ll a,ll b){return a>b?a:b;}
const int N=1e5+5,TL=3850;
int n,hd[3][N],xnt,to[3][N<<1],nxt[3][N<<1];
ll w[3][N<<1],dis[N],ds1[3005][3005],ans;
bool vis[N];
void add(int t,int x,int y,ll z){to[t][++xnt]=y;nxt[t][xnt]=hd[t][x];hd[t][x]=xnt;w[t][xnt]=z;}
int clk(){return (db)clock()/CLOCKS_PER_SEC*1000;}//ms
void dfs1(int cr,int fa,ll lj,int t,int rt)
{
  ds1[rt][cr]+=lj;
  for(int i=hd[t][cr],v;i;i=nxt[t][i])
    if((v=to[t][i])!=fa)dfs1(v,cr,lj+w[t][i],t,rt);
}
void solve1()
{
  for(int t=0;t<3;t++)
    for(int i=1;i<=n;i++)dfs1(i,0,0,t,i);
  for(int i=1;i<=n;i++)
    for(int j=i+1;j<=n;j++)ans=Mx(ans,ds1[i][j]);
  printf("%lld\n",ans);
}
void dfs(int cr,int fa,ll lj,int t)
{
  dis[cr]+=lj;
  for(int i=hd[t][cr],v;i;i=nxt[t][i])
    if((v=to[t][i])!=fa)dfs(v,cr,lj+w[t][i],t);
}
int cz(int rt)
{
  for(int i=1;i<=n;i++)dis[i]=0;
  for(int t=0;t<3;t++)dfs(rt,0,0,t);
  int ret=0;
  for(int i=1;i<=n;i++)
    {
      if(dis[i]>ans)ans=dis[i];
      if(!vis[i]&&dis[i]>dis[ret])ret=i;
    }
  return ret;
}
int main()
{
  n=rdn();ll z;
  for(int t=0;t<3;t++,xnt=0)
    for(int i=1,u,v;i<n;i++)
      u=rdn(),v=rdn(),z=rdn(),add(t,u,v,z),add(t,v,u,z);
  if(n<=5000){solve1();return 0;}
  int st=clk(); srand(time(0));srand(rand());
  int rt=rand()%n+1,T=0;
  while(clk()-st<TL)
    {
      T++; if(T==8)rt=rand()%n+1,T=0;
      if(vis[rt]){T=7;continue;}vis[rt]=1;
      rt=cz(rt);if(!rt)break;
    }
  printf("%lld\n",ans);
  return 0;
}

猜你喜欢

转载自www.cnblogs.com/Narh/p/10256881.html