HNOI2018题解
在此处输入标题
标签(空格分隔): 未分类
重做了一遍,本来以为很快的,结果搞了一天。。。
寻宝游戏
可以发现只有\(\&0\)和\(|1\)会对答案有影响
那么对于每一位,我们只要知道最后一个\(\&1\)和最后一个\(|1\)谁近就可以了。
发现并不好做,我们可以把操作串也当成\(01\)串,如果\(\&=0,|=1\)好像并没有什么用,于是我们令\(\&=1,|=0\)发现这样刚好满足了我们需要的信息,设\(op\)为操作串,这一位串为\(a\),如果\(op\)字典序小于\(a\)最后会是\(1\),否则为\(0\)。(\(|1=01,\&0=10\)这就是字典序了,手玩也可以
那么我们把原串基数排序,那么一定可以重排成\(00...011...1\),否则无解。如果有解那答案就是第一个\(1\)串代表的十进数值减掉最后一个\(0\)串十进制数值。注意下边界条件。
\(code\)
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
int k=0;char ch=gt;bool p=1;
while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
while(ch>'-')k=k*10+ch-'0',ch=gt;
return p?k:-k;
}
const int YL=1e9+7,N=1005,M=5005;
inline int ksm(int a,int k){int r=1;while(k){if(k&1)r=1ll*r*a%YL;a=1ll*a*a%YL,k>>=1;}return r;}
inline int MO(const int &x){return x>=YL?x-YL:x;}
int pw[M],s[N][M],id[2][M],rk[M],res[M];
int main()
{
int n=in(),m=in(),q=in();
for(int i=1;i<=n;++i)
{
static char S[M];scanf("%s",S+1);
for(int j=1;j<=m;++j)s[i][j]=S[j]-'0';
}
for(int i=1;i<=m;++i)id[0][i]=i;
int now=0;pw[0]=1;
for(int i=1;i<=n;++i)
{
now^=1;int cnt=0,tot=0;pw[i]=MO(pw[i-1]<<1);
for(int j=1;j<=m;++j)cnt+=s[i][j]^1;
for(int j=1;j<=m;++j)
if(s[i][id[now^1][j]])id[now][++cnt]=id[now^1][j];
else id[now][++tot]=id[now^1][j];
}
int *p=id[now];
for(int i=1;i<=m;++i)rk[p[i]]=i;
for(int i=1;i<=m;++i)
for(int j=1;j<=n;++j)
res[i]=MO(res[i]+s[j][i]*pw[j-1]);
res[m+1]=pw[n];p[m+1]=m+1;
while(q--)
{
static char S[M];scanf("%s",S+1);
int mx=-1,mi=m+1;
for(int i=1;i<=m;++i)
if(S[i]=='0')mx=std::max(mx,rk[i]);
else mi=std::min(mi,rk[i]);
if(mx>=mi){puts("0");continue;}
printf("%d\n",MO(res[p[mi]]-res[p[mx]]+YL));
}
return 0;
}
转盘
可以发现,走一圈是最优的。
那么即求\(min(max(T_j-j)+i)+n-1\)。
楼房重建即可
\(code\)
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
int k=0;char ch=gt;bool p=1;
while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
while(ch>'-')k=k*10+ch-'0',ch=gt;
return p?k:-k;
}
const int N=2e5+5;
int mis[N<<2],mip[N<<2],mxv[N<<2],a[N];
#define lc k<<1
#define rc k<<1|1
#define ls l, mid ,lc
#define rs mid+1,r,rc
#define mid ((l+r)>>1)
int calc(int mh,int l,int r,int k)
{
if(l==r)return std::max(mh,a[l])+l;int w=mxv[rc];
if(mh>=w)return std::min(calc(mh,ls),mh+mid+1);
else return std::min(calc(mh,rs),mis[k]);
}
inline void up(int l,int r,int k)
{
mxv[k]=std::max(mxv[lc],mxv[rc]);
mis[k]=calc(mxv[rc],ls);
}
void build(int l,int r,int k)
{
if(l==r)return mxv[k]=a[l],void();
build(ls),build(rs),up(l,r,k);
}
void upd(int l,int r,int k,int p)
{
if(l==r)return mxv[k]=a[l],void();
p<=mid?upd(ls,p):upd(rs,p);up(l,r,k);
}
int main()
{
int n=in(),m=in(),op=in(),ans=0;
for(int i=1;i<=n;++i)
a[i]=a[i+n]=in(),a[i]-=i,a[n+i]-=n+i;
build(1,n<<1,1);printf("%d\n",ans=mis[1]+n-1);
for(int i=1;i<=m;++i)
{
int x=in()^op*ans,y=in()^op*ans;
a[x]=y-x;a[x+n]=y-x-n;
upd(1,n<<1,1,x),upd(1,n<<1,1,x+n);
printf("%d\n",ans=mis[1]+n-1);
}
return 0;
}
毒瘤
之前的博客是我没理解清写的。
树的\(dp\)是基础,然后把返祖边们抠出来建虚树,枚举两端情况。
这里的\(f[u][0/1]\)是表示\(u\)取\(0/1\)的时候,子树的方案数。
所以我们只要枚举返祖边的祖先点的状态就可以了。
然后处理转移系数\(xs[u][i=0/1][j=0/1]\)表示虚树上的父亲选\(i\),这个点选\(j\)的系数的转移系数。
注意到边是有影响的,所以不在虚树上的点的\(xs\)表示的是该点选\(i\),这个点子树内第一个虚点选\(j\)的系数,当我们发现这个\(xs\)转移到一个虚点时,直接把\(xs\)挂在后面那维代表的虚点上。
不在虚树上的点记得乘上这个点选\(0/1\)的方案数。
在虚树上的点的\(xs\)要使得\(xs[u][0][0]=xs[u][1][1]=1\)。
转移的时候如果这个点被强制选了某个值,另一的\(dp\)初值必须为\(0\)。
\(code\)
#include<vector>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define pb push_back
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
int k=0;char ch=gt;bool p=1;
while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
while(ch>'-')k=k*10+ch-'0',ch=gt;
return p?k:-k;
}
const int YL=998244353,N=1e5+5;typedef std::vector<int> vi;
inline int ksm(int a,int k){int r=1;while(k){if(k&1)r=1ll*r*a%YL;a=1ll*a*a%YL,k>>=1;}return r;}
inline int MO(const int &x){return x>=YL?x-YL:x;}
vi G[N],E[N];int xs[N][2][2],f[N][2],g[N][2],tsz[N],imp[N],o[N];
int Eu[N],Ev[N],tot,dep[N],fg[N][2],vis[N],tt;
void pre_dfs(int u,int pa=0)
{
o[u]=++tt;
for(int v:G[u])if(v==pa)continue;
else if(!o[v])pre_dfs(v,u),tsz[u]+=tsz[v];
else
{
imp[u]=1;
if(o[u]<o[v])
Eu[++tot]=u,Ev[tot]=v;
}
imp[u]|=tsz[u]>=2;tsz[u]=tsz[u]||imp[u];
}
void mul(int f[2][2],int g[2][2])
{
int f00=f[0][0],f01=f[0][1];
int f10=f[1][0],f11=f[1][1];
g[0][0]=MO(f00+f10);
g[0][1]=MO(f01+f11);
g[1][0]=f00,g[1][1]=f01;
}
inline void init(int f[2][2]){f[0][0]=f[1][1]=1,f[0][1]=f[1][0]=0;}
int dfs(int u)
{
vis[u]=g[u][0]=g[u][1]=1;int pos=0;
for(int v:G[u])
if(!vis[v])
{
int w=dfs(v);
if(!w)
{
g[u][0]=1ll*g[u][0]*(g[v][1]+g[v][0])%YL;
g[u][1]=1ll*g[u][1]*g[v][0]%YL;
}
else if(!imp[u])mul(xs[v],xs[u]),pos=w;
else mul(xs[v],xs[w]),E[u].pb(w),pos=w;
}
if(imp[u])return init(xs[u]),u;
xs[u][0][0]=1ll*xs[u][0][0]*g[u][0]%YL;
xs[u][0][1]=1ll*xs[u][0][1]*g[u][0]%YL;
xs[u][1][0]=1ll*xs[u][1][0]*g[u][1]%YL;
xs[u][1][1]=1ll*xs[u][1][1]*g[u][1]%YL;
return pos;
}
void dp(int u)
{
f[u][0]=fg[u][1]?0:g[u][0];
f[u][1]=fg[u][0]?0:g[u][1];
for(int v:E[u])
{
dp(v);
for(int i=0;i<2;++i)
f[u][i]=(1ll*xs[v][i][0]*f[v][0]+1ll*xs[v][i][1]*f[v][1])%YL*f[u][i]%YL;
}
}
int main()
{
int n=in(),m=in(),ans=0;
for(int i=1,u,v;i<=m;++i)
u=in(),v=in(),G[u].pb(v),G[v].pb(u);
pre_dfs(1),imp[1]=1,dfs(1);int mx=1<<tot;
for(int i=0;i<mx;++i)
{
for(int j=0;j<tot;++j)
if(i>>j&1)fg[Eu[j+1]][1]=1,fg[Ev[j+1]][0]=1;
else fg[Eu[j+1]][0]=1;
dp(1);ans=MO(ans+MO(f[1][1]+f[1][0]));
for(int j=0;j<tot;++j)
if(i>>j&1)fg[Eu[j+1]][1]=0,fg[Ev[j+1]][0]=0;
else fg[Eu[j+1]][0]=0;
}
printf("%d\n",ans);
return 0;
}
游戏
我们发现如果一个点能到\(L\),且另一个点能到它,那么另一个点肯定能到\(L\),所以我们预处理\(L[i],R[i]\)为\(i\)点能扩大的最大范围。我们需要安排一个顺序使得他们最优。
如果一扇门\(x,x+1\)的钥匙在\(1-x\)则肯定要先转移\(x+1\)再转移\(x\),反之亦然。
于是我们可以拓扑排序。
问题的关键在于门是不满的,所以有很多没有关系的点之间跳来跳去,复杂度就假了。
但是由于数据只卡了正着做的,没卡反着做的,于是他的乱搞就能过(他写了两篇乱搞(小声。
那我们用并查集把没有门的点缩起来就\(ok\)了。
\(code\)
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
int k=0;char ch=gt;bool p=1;
while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
while(ch>'-')k=k*10+ch-'0',ch=gt;
return p?k:-k;
}
const int N=1e6+5;
int head[N],to[N],du[N],p[N],L[N],R[N],nxt[N],cnt,key[N],tot,n,fa[N];
inline void add(int u,int v){to[++cnt]=v,nxt[cnt]=head[u],head[u]=cnt,++du[v];}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
inline void work(int u)
{
int l=L[u],r=R[u],nl,nr;
while(1)
{
nl=l,nr=r;
while(l>1&&(!key[l-1]||(l<=key[l-1]&&key[l-1]<=r)))l=L[find(l-1)];
while(r<n&&(!key[ r ]||(l<=key[ r ]&&key[ r ]<=r)))r=R[find(r+1)];
if(nl==l&&nr==r)break;
}
L[u]=l,R[u]=r;
}
int main()
{
n=in();int m=in(),q=in();
for(int i=1,x,y;i<=m;++i)x=in(),y=in(),key[x]=y;
for(int i=1;i<=n;++i)fa[i]=i;
for(int i=1;i<n;++i)if(!key[i])fa[i+1]=find(i);
for(int i=1;i<n;++i)
if(key[i])
{
if(key[i]<=i)add(find(i+1),find(i));
else add(find(i),find(i+1));
}
std::queue<int>Q;
for(int i=1;i<=n;++i)if(fa[i]==i&&!du[i])Q.push(i);
while(!Q.empty())
{
int u=p[++tot]=Q.front();Q.pop();
for(int i=head[u];i;i=nxt[i])
if(!--du[to[i]])Q.push(to[i]);
}
for(int i=1;i<=n;++i)R[find(i)]=i;
for(int i=n;i>=1;--i)L[find(i)]=i;
for(int i=1;i<=tot;++i)work(p[i]);
while(q--){int x=find(in()),y=find(in());puts(L[x]<=y&&y<=R[x]?"YES":"NO");}
return 0;
}
排列
先把依赖关系的\(DAG\)建出来。
然后就是贪心,小的一定要尽量放在前面。
考虑当前最小值,它在父亲节点删掉后一定会被删。
所以可以并起来,然后我们现在是考虑一堆序列的顺序,推下式子发现只要平均值小就一定先选,就没了。
\(code\)
#include<cstdlib>
#include<vector>
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
#define mk std::make_pair
#define fr first
#define sc second
#define double long double
typedef std::pair<double,int> P;
inline int in()
{
int k=0;char ch=gt;bool p=1;
while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
while(ch>'-')k=k*10+ch-'0',ch=gt;
return p?k:-k;
}
typedef std::vector<int> vi;
const int N=5e5+5;vi G[N];
const double eps=1e-6;
int o[N],sz[N],fa[N],ff[N];ll w[N];
struct Queue
{
std::priority_queue<P>Q1,Q2;
void push(P x){Q1.push(x);}
void erase(P x){Q2.push(x);}
void upd(){while(!Q2.empty()&&Q1.top()==Q2.top())Q1.pop(),Q2.pop();}
inline void pop(){upd();Q1.pop();}
inline P top(){upd();return Q1.top();}
}Q;
int dfs(int u)
{
int ans=u!=0;o[u]=1;
for(int v:G[u])
if(o[v])puts("-1"),exit(0);
else ans+=dfs(v);return ans;
}
int find(int x){return x==ff[x]?x:ff[x]=find(ff[x]);}
int main()
{
int n=in();ll ans=0;
for(int i=1;i<=n;++i)G[fa[i]=in()].push_back(i);
for(int i=1;i<=n;++i)ans+=w[i]=in();
if(dfs(0)!=n)return puts("-1"),0;
for(int i=1;i<=n;++i)ff[i]=i,sz[i]=1;
for(int i=1;i<=n;++i)Q.push(mk(-(double)w[i],i));
for(int i=1;i<=n;++i)
{
P now=Q.top();Q.pop();int u=find(now.sc),v=find(fa[u]);
if(v)Q.erase(mk(-(double)w[v]/sz[v],v));
ans+=w[u]*sz[v],w[v]+=w[u],sz[v]+=sz[u],ff[u]=v;
if(v)Q.push(mk(-(double)w[v]/sz[v],v));
}
printf("%lld\n",ans);
return 0;
}
道路
普及\(dp\),设\(F[i][j][k]\)表示第\(i\)个城市,没修的公路有\(j\)条,没修的铁路有\(k\)条的最小代价。
叶子节点直接算,非叶子节点枚举修什么。考场上好像卡空间,用分治的\(fft\)的卡空间技巧就行了
\(code\)
#include<vector>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define gt getchar()
#define ll long long
#define File(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
typedef std::pair<int,int> P;
#define mk std::make_pair
#define fr first
#define sc second
inline int in()
{
int k=0;char ch=gt;bool p=1;
while(ch<'-')ch=gt;if(ch=='-')ch=gt,p=0;
while(ch>'-')k=k*10+ch-'0',ch=gt;
return p?k:-k;
}
typedef std::vector<int> vi;
const int N=20005;vi G[N];
ll f[N][41][41],a[N],b[N],c[N];
ll dfs(int u,int L,int R)
{
if(u<0)return c[-u]*(a[-u]+L)*(b[-u]+R);
if(~f[u][L][R])return f[u][L][R];int lc=G[u][0],rc=G[u][1];
return f[u][L][R]=std::min(dfs(lc,L+1,R)+dfs(rc,L,R),dfs(lc,L,R)+dfs(rc,L,R+1));
}
int main()
{
int n=in();
for(int i=1;i<n;++i)
{
int s=in(),t=in();
G[i].push_back(s),G[i].push_back(t);
}
for(int i=1;i<=n;++i)a[i]=in(),b[i]=in(),c[i]=in();
memset(f,-1,sizeof f);printf("%lld\n",dfs(1,0,0));
return 0;
}