HNOI2018 Day1

本文介绍了HNOI2018第一天三道题目的详细解题思路及代码实现,包括寻宝游戏、转盘与毒瘤等题目,通过巧妙的数据结构和算法优化实现了高效的解答。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

HNOI2018 Day1

bzoj5285 寻宝游戏

题目描述
bzoj:https://www.lydsy.com/JudgeOnline/problem.php?id=5285
loj:https://loj.ac/problem/2494

题解
把每一位拿出来,组成m个长度为n的二进制串。
把n个符号搞成一个序列,与符号用1表示,或符号用0表示。
可以发现,对于一个二进制位,如果二进制位上的二进制串小于等于符号位上的串,那么这一位就为1,否则就为0。
所以对于每一组询问,把每一位是0的和是1的分开,是0的取最大是1的取最小,求个差就是方案数。

代码

#include<bits/stdc++.h>
#define ll long long
#define mod 1000000007
#define N 1005
#define M 5005
using namespace std;
int n,m,q,pos[M];ll sum[M];
struct node{
  int s[N],id;
  bool operator<(const node &p)const{
    for(int i=1;i<=n;i++)
      if(s[i]!=p.s[i])return s[i]<p.s[i];
  }
  ll cal()
  {
    ll res=0;
    for(int i=1;i<=n;i++)res=(res*2+s[i])%mod;
    return res;
  }
}t[M];

int main()
{
  char x;
  scanf("%d%d%d",&n,&m,&q);
  for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
      scanf(" %c",&x),t[j].s[n-i+1]=x-48;
  for(int i=1;i<=m;i++)t[i].id=i;
  m++;for(int i=1;i<=n;i++)t[m].s[i]=1; 
  sort(t+1,t+m+1);
  for(int i=1;i<=m;i++)pos[t[i].id]=i;
  for(int i=1;i<=m;i++)sum[i]=t[i].cal();
  sum[m]=(sum[m]+1)%mod;
  while(q--)
  {
    int L=0,R=m;
    for(int i=1;i<m;i++)
    {
      scanf(" %c",&x);x-=48;
      if(x)R=min(R,pos[i]);
      else L=max(L,pos[i]);
    }
    if(L>=R)printf("0\n");
    else printf("%lld\n",(sum[R]-sum[L]+mod)%mod);
  }
  return 0;
}

bzoj5286 转盘

题目描述
传送门:
bzoj:https://www.lydsy.com/JudgeOnline/problem.php?id=5286
loj:https://loj.ac/problem/2495

题解
首先一个显然的结论,最优解一定是从某一个点出发绕一圈,一个点不会经过两次。
如果我们知道起点,那么从起点开始每个点依次减去1,2,3……,取个最大值再加上n-1就是答案。
所以我们把序列倍长。
对于全局问题,我们要求 min{i+max(t[j]-j)}+n-1 其中 1<=i<=n,i<=j<=i+n
我们可以把j的范围扩大到 i<=j<=n*2 ,显然不会有影响。

这个东西用线段树进行维护,对于每个节点,维护区间最大值maxn和该区间的答案res。
该区间的答案是指,以1~mid中的某个点出发绕一圈的最小值。

res的维护:
我们定义一个qry(l,r,val)操作。
一个区间的右半边的最大值为val,在左半边的一个子区间求最小值。
如果 (l,r) 区间上的右子区间的最大值小于val,那么右子区间的答案就是mid+1+res,左子区间继续递归。
否则的话,左子区间的答案就是当前区间的答案,右子区间继续递归。

这个维护很是精妙,根本想不到啊。
然后就AC了。

代码

#include<bits/stdc++.h>
#define N 200005
using namespace std;
int n,m,p,ans,s[N];
struct node{int res,maxn;}t[N*4];
class seg_tree
{
  int qry(int x,int l,int r,int val)
  {
    if(l==r)return l+max(t[x].maxn,val);
    int mid=l+r>>1,lc=x<<1,rc=lc+1;
    if(t[rc].maxn>=val)return min(t[x].res,qry(rc,mid+1,r,val));
    return min(mid+1+val,qry(lc,l,mid,val));
  }
  void update(int x,int l,int r)
  {
    int lc=x<<1,rc=lc+1;
    t[x].maxn=max(t[lc].maxn,t[rc].maxn);
    t[x].res=qry(lc,l,r,t[rc].maxn);
  }
  public:
  void build(int x,int l,int r)
  {
    if(l==r){
      t[x].maxn=s[l];t[x].res=s[l]+l;return;
    }
    int mid=l+r>>1,lc=x<<1,rc=lc+1;
    build(lc,l,mid);build(rc,mid+1,r);
    update(x,l,mid);
  }
  void modify(int x,int l,int r,int pos)
  {
    if(l==r){
      t[x].maxn=s[l];t[x].res=s[l]+l;return;
    }
    int mid=l+r>>1,lc=x<<1,rc=lc+1;
    if(pos<=mid)modify(lc,l,mid,pos);
    else modify(rc,mid+1,r,pos);
    update(x,l,mid);
  }
}T;

int main()
{
  int x,y;
  scanf("%d%d%d",&n,&m,&p);
  for(int i=1;i<=n;i++)
    scanf("%d",&s[i]),s[n+i]=s[i];
  for(int i=1;i<=n*2;i++)s[i]-=i;
  T.build(1,1,n*2);ans=t[1].res+n-1;
  printf("%d\n",ans);
  for(int i=1;i<=m;i++)
  {
    scanf("%d%d",&x,&y);
    if(p)x^=ans,y^=ans;
    s[x]=y-x;T.modify(1,1,n*2,x);
    s[x+n]=y-n-x;T.modify(1,1,n*2,x+n);
    printf("%d\n",ans=t[1].res+n-1);
  } 
  return 0;
} 

bzoj5287 毒瘤

题目描述
传送门:
bzoj:https://www.lydsy.com/JudgeOnline/problem.php?id=5287
loj:https://loj.ac/problem/2496

题解
首先m=n-1,那就是一棵树。
f[i][0]表示以i为根的子树 i 不选的情况,f[i][1]表示以i为根的子树 i 选的情况。
转移显然,f[i][0]=Π(f[son][0]+f[son][1])f[i][1]=Πf[son][1]
考虑多出来的m-n+1条边,因为nm差值不大,所以暴力枚举每条边的状态。
有三种状态,(0,0) , (1,0) , (0,1)。
可以合并成两种状态,(1,0) 和 (0,X)。
这样可以 O(2^(n0m+1)*k) ,可以拿70分。k表示非树边关联点的数量。

其实对于节点u,节点v对它的影响肯定可以化成这样的形式:
f[u][0]=k[v][0]*f[v][0]+k[v][1]*f[v][1]
继续优化,我们把非树边的关联点拿出来建一颗虚树,预处理出虚树上每个点到父亲的k的值。
然后dp一波就好了。
时间复杂度 O(2^(n-m+1)*k) ,可以AC。

代码

#include<bits/stdc++.h>
#define LL long long
#define mod 998244353
#define P 100
#define N 100010
#define M 200100
using namespace std;
int n,m,tag[N][2],tot,S,cnt,flag[N],dfn[N];
int k,la[N],fa[M],kk,ll[N],ff[P],mark[N];
LL g[N][2],f[N][2],ans;
struct node{int a,b;}e[M],ed[P];
struct info{
  LL x,y;
  info operator+(const info &p)const{
    return (info){(x+p.x)%mod,(y+p.y)%mod};
  }
  info operator*(const LL &num)const{
    return (info){x*num%mod,y*num%mod};
  }
}K[N][2];
struct edge{int a,b;info c,d;}ee[P];
void add(int a,int b)
{
  e[++k]=(node){a,b};fa[k]=la[a];la[a]=k;
  e[++k]=(node){b,a};fa[k]=la[b];la[b]=k;
}
void add2(int a,int b,info c,info d)
{
  ee[++kk]=(edge){a,b,c,d};
  ff[kk]=ll[a];ll[a]=kk;
}

int dfs(int x,int pre)
{
  int sum=0;dfn[x]=++cnt;
  for(int a=la[x];a;a=fa[a])
  {
    if(e[a].b==pre)continue;
    if(!dfn[e[a].b])sum+=dfs(e[a].b,x);
    else
    {
      mark[x]=1;
      if(dfn[x]<dfn[e[a].b])ed[++tot]=e[a];
    }
  }
  mark[x]|=(sum>=2);
  return (sum>0)|mark[x];
}

int solve(int x)
{
  int pos,res=0;flag[x]=g[x][0]=g[x][1]=1;
  for(int a=la[x];a;a=fa[a])
  {
    int y=e[a].b;if(flag[y])continue;
    pos=solve(y);
    if(!pos)
    {
      g[x][0]=g[x][0]*(g[y][0]+g[y][1])%mod;
      g[x][1]=g[x][1]*g[y][0]%mod;
    }
    else
    {
      if(mark[x])add2(x,pos,K[y][0]+K[y][1],K[y][0]);
      else K[x][0]=K[y][0]+K[y][1],K[x][1]=K[y][0],res=pos;
    }
  }
  if(mark[x])K[x][0]=(info){1,0},K[x][1]=(info){0,1},res=x;
  else K[x][0]=K[x][0]*g[x][0],K[x][1]=K[x][1]*g[x][1];
  return res;
}

void work(int x)
{
  f[x][0]=tag[x][0]*g[x][0];f[x][1]=tag[x][1]*g[x][1];
  for(int a=ll[x];a;a=ff[a])
  {
    int y=ee[a].b;work(y);
    f[x][0]=f[x][0]*(ee[a].c.x*f[y][0]%mod+ee[a].c.y*f[y][1]%mod)%mod;
    f[x][1]=f[x][1]*(ee[a].d.x*f[y][0]%mod+ee[a].d.y*f[y][1]%mod)%mod;
  }
}

int main()
{
  int a,b;
  scanf("%d%d",&n,&m);
  for(int i=1;i<=m;i++)
    scanf("%d%d",&a,&b),add(a,b);
  dfs(1,0);mark[1]=1;solve(1);S=(1<<tot);
  for(int i=1;i<=n;i++)if(mark[i])tag[i][0]=tag[i][1]=1;
  for(int s=0;s<S;s++)
  {
    for(int i=1;i<=tot;i++)
    {
      if((s>>i-1)&1)tag[ed[i].b][0]=tag[ed[i].a][1]=0;
      else tag[ed[i].b][1]=0;
    }
    work(1);ans=(ans+f[1][0]+f[1][1])%mod;
    for(int i=1;i<=tot;i++)
    {
      if((s>>i-1)&1)tag[ed[i].b][0]=tag[ed[i].a][1]=1;
      else tag[ed[i].b][1]=1;
    }
  }
  printf("%d\n",ans);
  return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值