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;
}