百度之星2018初赛A轮

百度之星2018初赛A轮

题目描述
http://bestcoder.hdu.edu.cn/contests/contest_show.php?cid=825

1001 度度熊拼三角

题解
签到题。
排个序枚举小的两条然后二分出第三条就好了。
其实排序之后只要看连续的三条就行了。

代码

#include
#define N 1005
using namespace std;
int n,s[N],ans; 
int main()
{
  int p;
  while(~scanf("%d",&n))
  {
    for(int i=1;i<=n;i++)scanf("%d",&s[i]);
    sort(s+1,s+n+1);ans=-1;
    for(int i=1;i<=n;i++)
      for(int j=i+1;j<=n;j++)
      {
        p=upper_bound(s+1,s+n+1,s[i]+s[j]-1)-s-1;
        while(s[p]>=s[i]+s[j]&&p)p--;
        if(p<=j)continue;
        ans=max(ans,s[i]+s[j]+s[p]);
      }
    printf("%d\n",ans);
  }
  return 0;
} 

1002 度度熊学队列

题解
当时脑子抽了写了个平衡树艹过去的。
其实用双向链表搞搞判判就好了。
贴的是平衡树的代码。

代码

#include
#define ll long long
#define N 400010
using namespace std;
int n,Q,cnt,rt[N],L=2147483647; 
struct node{
  int x;int lc,rc,tag,size;
  bool operator <(const node &p)const{
    int k=2000000207;
    return (ll)((k+=(k<<2)+1)&L)*(size+p.size)<(ll)(size)*L;
  }
  void clear(){x=0;lc=0;rc=0;tag=0;size=0;}
}t[N*10];
inline int get()
{
  char ch;int v;
  while(!isdigit(ch=getchar()));v=ch-48;
  while(isdigit(ch=getchar()))v=v*10+ch-48;
  return v;
}


class functional_trape
{
  public:
  void pushdown(int x)
  {
    if(!t[x].tag)return;
    int lc=t[x].lc,rc=t[x].rc;
    if(lc)swap(t[lc].lc,t[lc].rc),t[lc].tag^=1;
    if(rc)swap(t[rc].lc,t[rc].rc),t[rc].tag^=1;
    t[x].tag=0;
  }
  void split(int x,int &a,int &b,int k)
  {
    if(!x){a=0;b=0;return;}
    pushdown(x);
    if(t[t[x].lc].size>=k)
    {
      b=++cnt;t[b]=t[x];
      split(t[x].lc,a,t[b].lc,k);
      t[b].size-=t[a].size;
    }
    else
    {
      a=++cnt;t[a]=t[x];
      split(t[x].rc,t[a].rc,b,k-t[t[x].lc].size-1);
      t[a].size-=t[b].size;
    }
  }
  int merge(int a,int b)
  {
    if(!a)return b;
    if(!b)return a;
    int x=++cnt;
    if(t[a]x]=t[a];
      t[x].rc=merge(t[a].rc,b);
      t[x].size+=t[b].size;
    }
    else
    {
      pushdown(b);t[x]=t[b];
      t[x].lc=merge(a,t[b].lc);
      t[x].size+=t[a].size;
    }
    return x;
  }
  void insert(int &x,int w,int val)
  {
    int p=++cnt;t[p].x=val;t[p].size=1;
    if(!w)x=merge(p,x);
    else x=merge(x,p);
  }
  void erase(int &x,int w)
  {
    int a=0,b=0;
    if(!w)split(x,a,b,1),x=b;
    else split(x,a,b,t[x].size-1),x=a;
  }
  void reverse(int x)
  {
    swap(t[x].lc,t[x].rc);t[x].tag^=1;
  }
  int qry(int x,int w)
  {
    int k=w?t[x].size:1;
    while(1)
    {
      pushdown(x);
      if(t[t[x].lc].size+1==k)break;
      if(t[t[x].lc].size>=k)x=t[x].lc;
      else k-=t[t[x].lc].size+1,x=t[x].rc;
    }
    return t[x].x;
  }
  bool empty(int x){return t[x].size==0;}
}T;


int main()
{
  //freopen("1.in","r",stdin);
  //freopen("1.out","w",stdout);
  int tp,u,v,w,val;
  while(~scanf("%d%d",&n,&Q))
  {
    for(int i=1;i<=cnt;i++)t[i].clear();cnt=0;
    for(int i=1;i<=n;i++)rt[i]=0;
    while(Q--)
    {
      tp=get();
      if(tp==1)
      {
        u=get();w=get();val=get();
        T.insert(rt[u],w,val);
      }
      if(tp==2)
      {
        u=get();w=get();
        if(T.empty(rt[u])){printf("-1\n");continue;}
        printf("%d\n",T.qry(rt[u],w));
        T.erase(rt[u],w);
      }
      if(tp==3)
      {
        u=get();v=get();w=get();
        if(w)T.reverse(rt[v]);
        rt[u]=T.merge(rt[u],rt[v]);rt[v]=0;
      }
    }
  }
  return 0;
}

1003 度度熊剪纸条

题解
我们肯定会把若干段1剪开放在前缀。
一段1被剪开的代价是2(除非是开头的1或结尾的1)。
注意到每次操作最后可以有一段1不剪右边,放在1和0的分界点。记这种情况为特殊情况
所以定义f[i]表示剪i次,没有特殊情况的最长前缀。
g[i]表示剪i次,有特殊情况的最长前缀。注意特殊情况只能出现一次。
然后转移一波就好了。

代码

#include
#define N 10005
using namespace std;
int n,k,tot,len,ans,s[N],f[N],g[N],flag,tag;
int main()
{
  int len;char ch;
  while(~scanf("%d%d",&n,&k))
  {
    for(int i=0;i<=k;i++)f[i]=0,g[i]=0;
    for(int i=1;i<=tot;i++)s[i]=0;
    len=0;tot=0;ans=0;flag=0;tag=0;
    for(int i=1;i<=n;i++)
    {
      scanf(" %c",&ch);
      if(i==1&&ch=='1')tag=1;
      if(ch=='1')len++;
      else if(len)s[++tot]=len,len=0;
    }
    if(len)s[++tot]=len,flag=1;
    int st=1;
    if(tag)g[0]=s[1],st=2;
    for(int i=st;i<=tot;i++)
    {
      for(int j=k;j>=1;j--)
      {
        if(i==tot&&flag)
        {
          f[j]=max(f[j],f[j-1]+s[i]);
          g[j]=max(g[j],g[j-1]+s[i]);
        }
        else 
        {
          if(j>1)
          {
            f[j]=max(f[j],f[j-2]+s[i]);
            g[j]=max(g[j],g[j-2]+s[i]);
          }
          g[j]=max(g[j],f[j-1]+s[i]);
        }
      }
    }
    for(int i=0;i<=k;i++)ans=max(ans,max(f[i],g[i]));
    printf("%d\n",ans);
  }
  return 0;
}

1004 度度熊看球赛

题解
一开始以为是两排,一排n个座位,想了好久不会做。
后来发现是一排,总共2*n个座位,然后就会做了。
答案乘了个阶乘,所以求期望实际上是求方案数。
定义状态f[i][j]表示前i对情侣,有j对连在一起的方案数。
转移,考虑新加一对情侣对之前的影响。
新加一对情侣,可能增加一对相邻的情侣,或不变,或减少一对,或减少两对。
具体的转移式见代码。
这个题要先把dp离线算出来再做,不然会超时。

代码

#include
#define mod 998244353
#define ll long long
#define N 1005
using namespace std;
int n,m;ll f[N][N],ans;
int main()
{
  f[0][0]=1;
  for(int i=0;i<1000;i++)
    for(int j=0;j<=i;j++)
    {
      f[i+1][j]=(f[i+1][j]+f[i][j]*(2*j+(i+i-j+1)*(i+i-j)))%mod;
      f[i+1][j+1]=(f[i+1][j+1]+f[i][j]*2*(i+i+1-j))%mod;
      if(j>=1)f[i+1][j-1]=(f[i+1][j-1]+f[i][j]*2*j*(i+i-j+1))%mod;
      if(j>=2)f[i+1][j-2]=(f[i+1][j-2]+f[i][j]*j*(j-1))%mod;
    }
  while(~scanf("%d%d",&n,&m))
  {
    ll sum=1;ans=0;
    for(int i=0;i<=n;i++,sum=sum*m%mod)
      ans=(ans+f[n][i]*sum)%mod;
    cout<
  }
  return 0;
}

当时就过了这四题,然后就跑了。

1005 度度熊玩数组

题解
题目说的是每次让一个点失效,我们逆向思维,考虑每次让一个点生效。
这题关键就是这个逆向思维了。
然后每次生效一个点,我们就把它和它的左边右边合并。
合并的时候可以用启发式合并,这样就是log方的了。
至于区间和的计算,区间 [i,j] 和等价于 sum1[j]-sum[i-1],或 sum2[i]-sum2[j-1]。
所以对于每段,维护一下sum[i],在启发式合并的时候再另一边二分找的最近点就好了。

代码

#include
#define inf 999999999999999999ll
#define iter multiset::iterator
#define ll long long
#define N 100010
using namespace std;
int n,k,fa[N],s[N],p[N],flag[N];
ll sum1[N],sum2[N],ans[N],res;
multisett1[N],t2[N],t3[N],t4[N];

int find(int x)
{
  if(fa[x]==x)return x;
  return fa[x]=find(fa[x]);
}

void merge(int x,int y)
{ 
  int pos,tmp;
  x=find(x);y=find(y);
  if(!flag[x]||!flag[y])return;
  if(t1[x].size()<=t1[y].size())
  {
    for(iter it=t2[x].begin();it!=t2[x].end();++it)
    {
      iter p=t4[y].lower_bound((*it)-k);
      if(p!=t4[y].end())res=min(res,abs((*it)-(*p)-k));
      if(p!=t4[y].begin())--p,res=min(res,abs((*it)-(*p)-k));
    }
  }
  else
  {
    for(iter it=t1[y].begin();it!=t1[y].end();++it)
    {
      iter p=t3[x].lower_bound((*it)-k);
      if(p!=t3[x].end())res=min(res,abs((*it)-(*p)-k));
      if(p!=t3[x].begin())--p,res=min(res,abs((*it)-(*p)-k));
    }
    swap(x,y);
  }
  fa[x]=y; 
  for(iter it=t1[x].begin();it!=t1[x].end();++it)
    t1[y].insert(*it);
  for(iter it=t2[x].begin();it!=t2[x].end();++it)
    t2[y].insert(*it);
  for(iter it=t3[x].begin();it!=t3[x].end();++it)
    t3[y].insert(*it);
  for(iter it=t4[x].begin();it!=t4[x].end();++it)
    t4[y].insert(*it);
  t1[x].clear();t2[x].clear();
  t3[x].clear();t4[x].clear();
}

int main()
{
  while(~scanf("%d%d",&n,&k))
  {
    sum2[n+1]=0;res=inf;
    for(int i=1;i<=n;i++)fa[i]=i,flag[i]=0;
    for(int i=1;i<=n;i++)scanf("%d",&s[i]);
    for(int i=1;i<=n;i++)
    {
      sum1[i]=sum1[i-1]+s[i];
      t1[i].clear();t1[i].insert(sum1[i]);
      t3[i].clear();t3[i].insert(sum1[i-1]);
    }
    for(int i=n;i;i--)
    {
      sum2[i]=sum2[i+1]+s[i];
      t2[i].clear();t2[i].insert(sum2[i]);
      t4[i].clear();t4[i].insert(sum2[i+1]);
    }
    for(int i=1;i<=n;i++)scanf("%d",&p[i]);
    for(int i=n;i;i--)
    {
      flag[p[i]]=1;
      if(p[i]>1)merge(p[i]-1,p[i]);
      if(p[i]1);
      res=min(res,(ll)abs(s[p[i]]-k));
      ans[i]=res;
    }
    for(int i=1;i<=n;i++)printf("%I64d\n",ans[i]);
  }
  return 0;
} 

你可能感兴趣的:(套题总结,题解,dp及其优化,逆向思维,启发式合并)