CF1092 D & E —— 思路+单调栈,树的直径

题目:https://codeforces.com/contest/1092/problem/D1

https://codeforces.com/contest/1092/problem/D2

https://codeforces.com/contest/1092/problem/E

很有趣的题;

对于D1,首先发现两种砖的放法和高度的奇偶性有关(!);

而竖着放的砖不改变一列的奇偶性,也就是确定一列的奇偶性后,它的高度是可以任意的,那么我们就不用考虑实际高度的问题了;

然后发现,如果两列奇偶性相同的列相邻了,那么它们就“无敌”了,可以变成任意高度;

而两列可以合并,只能是它们相邻且奇偶性相同;

这就很像两组括号序列啊!奇数是 (),偶数是 [],那么整个序列就是 (, ), [, ] 相间的;

只要栈顶能完成一个匹配,就弹栈表示这两列“无敌”了;

所以最后要是栈里没有元素或只剩下一个元素,序列就是合法的,否则不合法;

这么简单就做完了!

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int const xn=2e5+5;
int n,sta[xn],top;
int rd()
{
  int ret=0,f=1; char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return f?ret:-ret;
}
int main()
{
  n=rd();
  for(int i=1,x;i<=n;i++)
    {
      x=rd();
      if(top&&(x&1)==(sta[top]&1))top--;
      else sta[++top]=x;
    }
  if(top<=1)puts("YES");
  else puts("NO");
  return 0;
}
D1

对于D2,只能放横着的砖;

那就更简单了,每次先填满最低的一段,如果其长度是奇数就不合法了,否则就和旁边的合并成一段;

这个过程可以用单调栈维护。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int const xn=2e5+5;
int n,sta[xn],top,len[xn],a[xn];
int rd()
{
  int ret=0,f=1; char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return f?ret:-ret;
}
int main()
{
  n=rd();
  for(int i=1;i<=n;i++)a[i]=rd(); 
  for(int i=1,l,j;i<=n;i=j+1)
    {
      j=i; l=0;
      while(a[i]==a[j+1])j++;
      while(top&&a[i]>sta[top])
    {
      if(len[top]&1){puts("NO"); return 0;}
      l=len[top]; top--;
      if(top&&sta[top]<a[i])len[top]+=l;
      else break;
    }
      while(top&&sta[top]==a[i])l+=len[top--];
      sta[++top]=a[i]; len[top]=l+(j-i+1);
      //printf("sta[%d]=%d len=%d\n",top,sta[top],len[top]);
    }
  int l=0;
  while(top>1)
    {
      l+=len[top--];
      if(l&1){puts("NO"); return 0;}
    }
  puts("YES");
  return 0;
}
D2

E题要最小化连通块合成的树的直径,还要输出连边方案;

结论就是找到所有连通块(小树)的直径中点,最大的直径的中点连接所有其他中点;

想想果然很有道理,因为这样其实最终的直径还是最大连通块的直径,除非有几个连通块一样都是最大;

而其他连法都可能让直径更大;

所以只需要找直径中点即可,看了看提交记录,发现可以写得很优美,就是 dfs 同时找直径端点、长度和中点;

因为如果从一端 dfs 到另一端,那么中点一定在路径上,返回的时候记录一下即可;

然后注意特别判断有两个或三个一样大的最大连通块;

直径的性质要好好利用。

代码如下:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define pb push_back
using namespace std;
int const xn=1005;
int n,hd[xn],ct,to[xn<<1],nxt[xn<<1],dis[xn],mx,d;
int vis[xn];
struct N{
  int x,d;
  N(int x=0,int d=0):x(x),d(d) {}
  bool operator < (const N &y) const
  {return d<y.d;}
};
vector<N>v;
int rd()
{
  int ret=0,f=1; char ch=getchar();
  while(ch<'0'||ch>'9'){if(ch=='-')f=0; ch=getchar();}
  while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
  return f?ret:-ret;
}
void add(int x,int y){to[++ct]=y; nxt[ct]=hd[x]; hd[x]=ct;}
void dfs(int x)
{
  vis[x]=1;
  if(dis[x]>mx)mx=dis[x],d=x;
  for(int i=hd[x],u;i;i=nxt[i])
    if(vis[u=to[i]]!=1)dis[u]=dis[x]+1,dfs(u);
}
bool dfsx(int x)
{
  vis[x]=2; bool ret=0;
  if(dis[x]>mx)mx=dis[x],ret=1;
  for(int i=hd[x],u;i;i=nxt[i])
    if(vis[u=to[i]]!=2)
      {
    dis[u]=dis[x]+1;
    if(dfsx(u))ret=1;
      }
  if(ret&&dis[x]==mx/2)d=x;//
  return ret;
}
int main()
{
  n=rd(); int m=rd(); int ans=0;
  for(int i=1,x,y;i<=m;i++)x=rd(),y=rd(),add(x,y),add(y,x);
  for(int i=1;i<=n;i++)
    if(!vis[i])
      {
    dis[i]=0; mx=-1; dfs(i);
    dis[d]=0; mx=-1; dfsx(d); 
    ans=max(ans,mx); v.pb(N(d,mx));
      }
  sort(v.begin(),v.end()); int siz=v.size();
  if(siz>1)
    {
      ans=max(ans,(v[siz-1].d+1)/2+(v[siz-2].d+1)/2+1);//
      if(siz>2)ans=max(ans,(v[siz-2].d+1)/2+(v[siz-3].d+1)/2+2);//
    }
  printf("%d\n",ans);
  int u=v[siz-1].x;
  for(int i=0;i<siz-1;i++)
    printf("%d %d\n",u,v[i].x);
  return 0;
}
E

 

posted @ 2018-12-21 22:35  Zinn  阅读(283)  评论(0编辑  收藏  举报