作为一名高二老年选手来补一下我省去年的省选题。

D1T1:寻宝游戏

按顺序给出\(n\)个\(m\)位的二进制数\(a_i\),再在最前方添一个\(0\),

给出\(q\)次询问,每次询问给出一个同样长为\(m\)的二进制数\(r_i\),

要求在之前给出的\(n+1\)个二进制数的每相邻两个数的空位添加按位与运算符按位或运算符,

一共\(n\)个,并使得这个算式得到的值为\(r_i\),求方案数。

\(n,q\le 1000,m\le 5000\)

暴力\(30\%\)不提。

对每一位分开考虑。

根据 [NOI2014]起床困难综合症 的理论或者自己手推,我们可以得到如下结论:

考虑\(r_i\)的第\(j\)位和在其之前的\(n\)个\(bit\),可以知道如果\(|0\)或者\(\&1\)对结果没有影响;

如果这一位上是\(1\),那么情况应该是在最后一个\(|1\)后不存在\(\&0\);

如果这一位上是\(0\),那么情况应该是在最后一个\(\&0\)后不存在\(|1\),或者既没有\(\&0\)也没有\(|1\)。

你看这里的条件都和最后一次进行的操作相关,所以我们倒着确定每一次放入的符号:

我们来看一看\(a_n\)和\(r_i\)对应位置上的不同情况。

\(x?0=0,x?1=1\) : \(\&\ |\)均可。

\(x?0=1\):如果放入\(\&\)运算符则结果必定为\(0\),因此这个运算符只能为\(|\);

\(x?1=0\):如果放入\(|\)运算符则结果必定为\(1\),因此这个运算符只能为\(\&\)。

综上所述,我们可以得出:

如果\(a_n\)和\(r_i\)同时出现了\(0-1,1-0\)两种情况,显然无解;

否则,如果\(a_n\)和\(r_i\)有任何一位不同\((0-1/1-0)\),那么运算符是可以唯一确定的;

此时将需要考虑的行减少一些并继续考虑\(a_{n-1}\);

否则,可以知道此时\(a_n=r_i\);

此时要分\(r_i\)是否含有\(0/1\)进行讨论:

如果\(r_i\)全为\(0\),那么在\(a_n\)前添加\(\&\)后,\(a_{1-n-1}\)之前的运算符可以随机添加;

答案加上\(2^{n-1}\),然后递归考虑添加\(|\)的情况;

\(r_i\)全为\(1\)同理。

如果\(r_i\)即有\(1\)又有\(0\),只能分别进行递归。

但是,在这一次递归之后仅需考虑\(r_i\)剩下的全为\(1\)的部分 或 剩下的全为\(0\)的部分,

这意味着之后不会再次出现这种情况。

使用\(bitset\)优化运算,复杂度为\(O(\frac{nmq}{32})\),可以通过\(70\%\)的数据点。O2就过了

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=1e3+10;
const int mod=1e9+7;
inline ll read(){
ll data=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
return data*w;
}
inline void file(){
freopen(FL".in","r",stdin);
freopen(FL".out","w",stdout);
}
inline void upd(int &a,int b){a+=b;if(a>=mod)a-=mod;}
inline void dec(int &a,int b){a-=b;if(a<0)a+=mod;}
inline int poww(int a,int b){
int res=1;
for(;b;b>>=1,a=1ll*a*a%mod)
if(b&1)res=1ll*a*res%mod;
return res;
}
int n,m,q,ans,pw[1005];char s[5005];
typedef bitset<5000> line;
line a[N],b[N],r,rv,e[N],f[N],u,tmp;
inline void solve(int x,line now){
if(!x){if(!(r&now).any())upd(ans,1);return;}
int c=(e[x]&now).any(),d=(f[x]&now).any();
if(!c&&!d){
if((r&now).any()&&(rv&now).any())
solve(x-1,now&a[x]),solve(x-1,now&b[x]);
else upd(ans,pw[x-1]),solve(x-1,now);
}
if(!c&&d)solve(x-1,now&b[x]);
if(c&&!d)solve(x-1,now&a[x]);
}
int main()
{
n=read();m=read();q=read();
register int i,j;
for(i=0;i<m;i++)u.set(i);
for(i=pw[0]=1;i<=n;i++)pw[i]=2ll*pw[i-1]%mod;
for(i=1;i<=n;i++){
scanf("%s",s+1);
for(j=1;j<=m;j++)
if(s[j]=='1')a[i].set(j-1);
b[i]=u^a[i];
}
for(i=1;i<=q;i++){
scanf("%s",s+1);r.reset();
for(j=1;j<=m;j++)
if(s[j]=='1')r.set(j-1);
rv=u^r;
for(j=1;j<=n;j++){tmp=a[j]^r;e[j]=tmp&a[j];f[j]=tmp&r;}
ans=0;solve(n,u);printf("%d\n",ans);
}
return 0;
}

考虑再次进行转化。发现分开考虑每一位后得出的结论类似于比较两个数的大小关系,

于是我们将一种方案抽象为一个长为\(n\)的二进制数\(now\),

\(0\)表示\(|\),\(1\)表示\(\&\),\(0-0\)表示在\(0\)前插入\(|\)。

那么我们之前推出的结论可以转化为:

考虑\(r_i\)的第\(j\)位和在其之前的\(n\)个\(bit\),

\(0-0\)和\(1-1\)对结果没有意义;

如果这一位上是\(1\),那么在最后一个\(0-1\)之后不存在\(1-0\);

如果这一位上是\(0\),那么在最后一个\(1-0\)之后不存在\(0-1\),或者不存在\(1-0/0-1\)的情况;

可以发现如果将这\(n\)个\(bit\)转化为一个二进制数\(b_j\),

这就是一个严格小于\((now<b_j)\)和大于等于\((now\ge b_j)\)。

那么对于每个询问扣出其边界\(x\le now <y\),那么方案数就是两者之间的二进制数的个数。

对转化后的\(b_j\)排个序就好了。

复杂度为\(O(nmlogn+qm)\),可以通过\(100\%\)的数据点。

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=1e3+10;
const int M=5e3+10;
const int mod=1e9+7;
inline ll read(){
ll data=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
return data*w;
}
inline void file(){
freopen(FL".in","r",stdin);
freopen(FL".out","w",stdout);
}
int n,m,q;char s[M];
int pw[N],a[N][M],r[N][M];
int val[M],o[M],p[M];
inline void upd(int &a,int b){a+=b;if(a>=mod)a-=mod;}
inline void dec(int &a,int b){a-=b;if(a<0)a+=mod;}
inline bool cmp(int i,int j){
for(int k=n;k;k--)
if(a[k][i]!=a[k][j])return a[k][i]<a[k][j];
return 0;
}
int main()
{
n=read();m=read();q=read();
for(int i=pw[0]=1;i<=n+1;i++)
pw[i]=2ll*pw[i-1]%mod;
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++)
a[i][j]=s[j]-48;
}
for(int i=1;i<=q;i++){
scanf("%s",s+1);
for(int j=1;j<=m;j++)
r[i][j]=s[j]-48;
}
val[0]=0;val[m+1]=pw[n];o[m+1]=m+1;
for(int i=1;i<=m;i++)
for(int j=n;j;j--)
if(a[j][i])upd(val[i],pw[j-1]);
for(int i=1;i<=m;i++)o[i]=i;
sort(o+1,o+m+1,cmp);
for(int i=1;i<=m;i++)p[o[i]]=i;
for(int i=1,x,y,res;i<=q;i++){
x=0;y=m+1;
for(int j=1;j<=m;j++)
if(r[i][j])y=min(y,p[j]);
else x=max(x,p[j]);
if(x>=y){puts("0");continue;}
dec(res=val[o[y]],val[o[x]]);
printf("%d\n",res);
}
return 0;
}

D1T2:转盘

一个转盘上有摆成一圈的\(n\)个物品(编号1~\(n\)),其中的\(i\)个物品会在\(t_i\)时刻出现。

在0时刻时,小G可以任选\(n\)个物品中的一个,我们将其编号为\(s_0\)。

并且如果\(i\)时刻选择了物品\(s_i\),那么\(i+1\)时刻可以继续选择当前物品或选择下一个物品(\(s_i\%n+1\))。

在每一时刻(包括\(0\)时刻),如果小G选择的物品已经出现了,那么小G将会标记它。

小H想知道,在物品选择的最优策略下,小G什么时候能标记所有物品?

\(n,m,t_i\le 10^5\),带修+强制在线。

首先我们知道最优方案一定可以只绕一圈。

理由是假设最优方案中最后标记的哪一个物品为\(x\),

那么第一次直接从\(x+1\)开始然后等到其出现显然不会更劣。

直接暴力枚举起点维护答案时间复杂度为\(O(n^2m)\)。

考虑稍作转化,记录一个长度为\(2n\)的数组\(a\),\(a_i=T_i-i,a_{i+n}=a_{i}\),

那么答案为\(min_{i=1}^n\{max_{j=1}^n\{a_{i+j-1}\}+i\}+n-1\)。

经典的滑动窗口问题,使用单调队列维护最大值,时间复杂度降为\(O(nm)\)。

现在考虑如何快速维护这\(n\)个长为\(n\)的窗口。

根据题目性质,\(a_{i+n}=t_i-(i+n)<t_i-i=a_i\),所以只要维护起点为\(1-n\),长度\(\ge n\)的窗口即可。

那么我们可以维护\(min_{i=1}^n\{max_{j=i}^{2n}\{a_{i+j-1}\}+i\}+n-1\)

于是考虑在线段树上维护最大值\(mx[x]\)和\(ans[x]=min_{i=l}^{mid}\{max_{j=i}^r\{a_j\}+i\}\),根据各个节点的情况讨论一下进行修改即可。

最后一个节点表示的区间为\([1,2n]\),则\(ans[rt]\)即为所求。

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=2e5+20;
const int inf=2147483647;
const int mod=998244353;
inline ll read(){
ll data=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
return data*w;
}
inline void file(){
freopen(FL".in","r",stdin);
freopen(FL".out","w",stdout);
}
int n,m,p,a[N];
int mx[N<<2],ans[N<<2];
#define ls (i<<1)
#define rs (i<<1|1)
#define mid ((l+r)>>1)
inline int getans(int i,int l,int r,int x){
if(l==r)return l+max(mx[i],x);
if(mx[rs]>=x)return min(ans[i],getans(rs,mid+1,r,x));
return min(getans(ls,l,mid,x),mid+1+x);
}
inline void update(int i,int l,int r){
mx[i]=max(mx[ls],mx[rs]);ans[i]=getans(ls,l,mid,mx[rs]);
}
void build(int i,int l,int r){
if(l==r){mx[i]=a[l];ans[i]=inf;return;}
build(ls,l,mid);build(rs,mid+1,r);update(i,l,r);
}
void insert(int i,int l,int r,int p,int x){
if(l==r){mx[i]=x;return;}
p<=mid?insert(ls,l,mid,p,x):insert(rs,mid+1,r,p,x);update(i,l,r);
}
int main()
{
n=read();m=read();p=read();
for(int i=1;i<=n;i++)a[i]=a[i+n]=read();
for(int i=1;i<=2*n;i++)a[i]-=i;
int res;build(1,1,2*n);printf("%d\n",res=ans[1]+n-1);
for(int i=1,x,y;i<=m;i++){
x=read();y=read();if(p)x^=res,y^=res;
a[x]=y-x;insert(1,1,2*n,x,y-x);
a[x+n]=y-x-n;insert(1,1,2*n,x+n,y-x-n);
printf("%d\n",res=ans[1]+n-1);
}
return 0;
}

同时感谢litble的题解教会了我做这道题。

D1T3:毒瘤

求\(n\)个点\(m\)条边的独立集方案数。

\(n\le 10^5,m\le n+10\)

令\(k=m-n+1\),表示这个图比树多出了\(k\)条边。

一个简单的想法是暴力枚举\(k\)条边所对应的\(2k\)个点的情况然后\(O(n)\ dp\),可以获得\(55\)分。

写的时候已经知道要用虚树了,所以我强行把这\(2k\)个点套了一棵虚树上去,

然后我的\(dp\)状态是\(f[i][S]\)表示当前节点子树内对应选择情况的方案数,\(S\)的大小是\(2^{2k}\);

预处理出虚树的每一条边的两个端点在不同选择情况下对应的方案数,预处理的总复杂度是\(O(n)\)。

树形\(dp\)的同时将子树内关键点的选择情况合并,根据树形背包的复杂度,

这个东西的复杂度好象是\(O(2^{2k})\)。

发现不能直接开这么大的数组,所以使用指针根据子树内关键点的大小动态分配内存,空间减小了一半;

然后就卡着时间卡着空间过了这题。(

下面放一放我这题的丑陋代码

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=1e5+20;
const int mod=998244353;
inline ll read(){
ll data=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
return data*w;
}
inline void file(){
freopen(FL".in","r",stdin);
freopen(FL".out","w",stdout);
} void print(int x,int d){if(d)print(x>>1,d-1),putchar(48+(x&1));}
inline void upd(int &a,int b){a+=b;if(a>=mod)a-=mod;}
inline void dec(int &a,int b){a-=b;if(a<0)a+=mod;}
inline int poww(int a,int b){
int res=1;
for(;b;b>>=1,a=1ll*a*a%mod)
if(b&1)res=1ll*a*res%mod;
return res;
} int n,m,e,rt,ans;
struct edge{int u,v;}E[N];
int F[N];int find(int x){return F[x]?F[x]=find(F[x]):x;}
int head[N],nxt[N<<1],to[N<<1],cnt;
inline void add(int u,int v){to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;}
int headv[N],nxtv[N<<1],tov[N<<1],sum[N<<1][2][2],cnte;
inline void addv(int u,int v){
tov[++cnte]=v;nxtv[cnte]=headv[u];headv[u]=cnte;
} int fa[N],dep[N],sz[N],son[N],top[N],w[N],fw[N],cntw;
inline bool cmp_w(int i,int j){return w[i]<w[j];}
void dfs1(int u,int ff){
fa[u]=ff;dep[u]=dep[ff]+1;sz[u]=1;son[u]=0;
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];if(v==ff)continue;
dfs1(v,u);sz[u]+=sz[v];
if(sz[son[u]]<sz[v])son[u]=v;
}
}
void dfs2(int u,int tp){
top[u]=tp;w[u]=++cntw;fw[cntw]=u;
if(son[u])dfs2(son[u],tp);
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];if(v==fa[u]||v==son[u])continue;
dfs2(v,v);
}
}
inline int lca(int u,int v){
while(top[u]!=top[v])
dep[top[u]]>dep[top[v]]?u=fa[top[u]]:v=fa[top[v]];
return dep[u]<dep[v]?u:v;
} int t[N],id[N],k,p,cntk,cal[N],cov,col[N];
int f[N][2],vis[N];
void dfs3(int u,int ff){
f[u][0]=f[u][1]=1;vis[u]=1;
if(id[u]!=-1){f[u][col[u]^1]=0;return;}
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];if(v==ff)continue;dfs3(v,u);
f[u][0]=1ll*(f[v][0]+f[v][1])%mod*f[u][0]%mod;
f[u][1]=1ll*f[v][0]*f[u][1]%mod;
}
}
int sub[N],lw[N];
int low[1<<22],dp[33554432],len,*g[N][2],siz[N];
void dfs4(int u,int ff){
static int tmp[1<<22];
vis[u]=1;if(id[u]<k)id[u]=cntk++,siz[u]=1,lw[u]=1<<id[u];
for(int i=headv[u],v;i;i=nxtv[i]){
v=tov[i];if(v==ff)continue;
dfs4(v,u);siz[u]+=siz[v];lw[u]|=sub[v];
for(int c=0;c<2;c++){
col[u]=c;dfs3(fa[v],v);
sum[i][c][0]=(f[fa[v]][0]+f[fa[v]][1])%mod;
sum[i][c][1]=f[fa[v]][0];
}
}
lw[u]=low[lw[u]];
g[u][0]=dp+len;len+=1<<siz[u]+1;
g[u][1]=dp+len;len+=1<<siz[u]+1;
g[u][0][0]=g[u][1][0]=1;
for(int i=head[u],v;i;i=nxt[i]){
v=to[i];if(v==fa[u]||vis[v])continue;dfs3(v,u);
g[u][0][0]=1ll*(f[v][0]+f[v][1])*g[u][0][0]%mod;
g[u][1][0]=1ll*f[v][0]*g[u][1][0]%mod;
}
for(int i=headv[u],v;i;i=nxtv[i]){
v=tov[i];if(v==ff)continue;
for(int c=0;c<2;c++){
for(int all=(sub[u]|sub[v])>>lw[u],s=all;;s=(s-1)&all){
tmp[s]=0;if(!s)break;
}
for(int d=0;d<2;d++)
for(int s=sub[u];;s=(s-1)&sub[u]){
for(int t=sub[v];;t=(t-1)&sub[v]){
if(g[u][c][s>>lw[u]]&&g[v][d][t>>lw[v]]&&sum[i][c][d])
upd(tmp[(s|t)>>lw[u]],1ll*g[u][c][s>>lw[u]]*g[v][d][t>>lw[v]]%mod*sum[i][c][d]%mod);
if(!t)break;
}
if(!s)break;
}
for(int all=(sub[u]|sub[v])>>lw[u],s=all;;s=(s-1)&all){
g[u][c][s]=tmp[s];if(!s)break;
}
}
sub[u]|=sub[v];
}
if(id[u]<k){
for(int all=(sub[u]|1<<id[u])>>lw[u],s=all;;s=(s-1)&all){
tmp[s]=0;if(!s)break;
}
for(int s=sub[u];;s=(s-1)&sub[u]){
upd(tmp[(s|1<<id[u])>>lw[u]],g[u][1][s>>lw[u]]);
if(!s)break;
}
for(int all=(sub[u]|1<<id[u])>>lw[u],s=all;;s=(s-1)&all){
g[u][1][s]=tmp[s];if(!s)break;
}
sub[u]|=1<<id[u];
}
} int main()
{
n=read();m=read();memset(id,-1,sizeof(id));
for(int i=1,u,v,fu,fv;i<=m;i++){
u=read();v=read();fu=find(u);fv=find(v);
if(fu!=fv)F[fu]=fv,add(u,v),add(v,u);
else{
if(id[u]==-1){t[k]=u;id[u]=k;k++;}
if(id[v]==-1){t[k]=v;id[v]=k;k++;}
E[++e]=(edge){u,v};
}
}
if(!k)return dfs3(1,0),printf("%d\n",ans=(f[1][0]+f[1][1])%mod),0;
for(rt=1;id[rt]==-1;rt++);
dfs1(rt,0);dfs2(rt,0);
sort(t,t+k,cmp_w);p=k;
for(int i=1;i<k;i++){
t[p]=lca(t[i],t[i-1]);
if(id[t[p]]==-1)id[t[p]]=p,p++;
}
for(int s=1;s<(1<<k);s++)low[s]=s&1?0:low[s>>1]+1;
sort(t,t+p);p=unique(t,t+p)-t;sort(t,t+p,cmp_w);
for(int i=0;i<p;i++){
while(cov&&w[cal[cov]]+sz[cal[cov]]-1<w[t[i]])cov--;
if(cov)addv(cal[cov],t[i]),addv(t[i],cal[cov]);
cal[++cov]=t[i];
}
dfs4(rt,0);
for(int c=0;c<2;c++)
for(int s=sub[rt];;s=(s-1)&sub[rt]){
bool pd=1;
for(int i=1;i<=e;i++)
if((s&1<<id[E[i].u])&&(s&1<<id[E[i].v])){pd=0;break;}
if(pd)upd(ans,g[rt][c][s>>lw[rt]]);
if(!s)break;
}
printf("%d\n",ans);
return 0;
}

上面的愚蠢做法实际上忽略了一个重要的优化:只需要枚举多出的边中每一对点的选择情况。

就算直接枚举\(0-0,0-1,1-0\)三种情况也比上面的方法要好。

实际只须枚举两种情况:某个节点不选,另一个节点随意/这个节点要选,对应的节点强制不选。

然后直接\(dp\)就能有\(75\)分,再加上虚树即可无压力\(O(n+k2^k)\ AC\)。

D2T1:游戏

感觉是个神仙题啊,不知道为什么你们都把它当sb题切

看到\(y\le x\)的部分分感觉可以线段树上二分暴力搞搞,于是觉得正解也可以这样做

于是就陷入了无穷无尽的调试中...

我们考虑比暴力更加优秀一些的方法:记忆化搜索。然后就过了此题

首先我们把没有上锁的房间连成一块。

到了一个新的房间时,我们要保证它是已经被搜索过了的。

一个很简单的想法是,对于一扇上锁的门,如果钥匙在它左边,那么我们肯定先求解位于门右边的房间的答案,如果从左边可以到达右边,那么直接加上右边搜索的结果即可。

那么我们对于一扇上锁的门,从钥匙所在方向的反方向的房间向钥匙所在的方向的房间连边,代表先求解反方向;

然后按照拓扑序依次进行求解即可。

#include<bits/stdc++.h>
#define FL "game"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=1e6+20;
const int inf=2147483647;
const int mod=998244353;
inline ll read(){
ll data=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
return data*w;
}
inline void file(){
freopen(FL".in","r",stdin);
freopen(FL".out","w",stdout);
} int n,m,k,q,key[N],id[N];
int d[N],head[N],nxt[N<<1],to[N<<1],cnt;
inline void add(int u,int v){
d[v]++;to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
queue<int>Q;int L[N],R[N]; inline void solve(int i){
bool sl=0,sr=0;
while(!sl||!sr){
sl=sr=0;
if(L[i]==1)sl=1;
else{
if(L[i]<=key[L[i]-1]&&key[L[i]-1]<=R[i]){
L[i]=L[L[i]-1];sl=sr=0;
}
else sl=1;
}
if(R[i]==k)sr=1;
else{
if(L[i]<=key[R[i]]&&key[R[i]]<=R[i]){
R[i]=R[R[i]+1];sl=sr=0;
}
else sr=1;
}
}
} int main()
{
n=read();m=read();q=read();
for(int i=1,x,y;i<=m;i++){
x=read();y=read();key[x]=y;
}
for(int i=k=1;i<=n;i++){id[i]=k;if(key[i])k++;}
for(int i=1;i<n;i++)if(key[i])key[id[i]]=id[key[i]];
for(int i=1;i<=k;i++)L[i]=R[i]=i;
for(int i=1;i<k;i++)key[i]<=i?add(i+1,i):add(i,i+1);
for(int i=1;i<=k;i++)if(!d[i])Q.push(i);
while(!Q.empty()){
int u=Q.front();Q.pop();solve(u);
for(int i=head[u],v;i;i=nxt[i]){
d[v=to[i]]--;if(!d[v])Q.push(v);
}
}
for(int i=1,s,t;i<=q;i++){
s=read();t=read();s=id[s];t=id[t];
if(t<L[s]||R[s]<t)puts("NO");
else puts("YES");
}
return 0;
}

D2T2:排列

给定\(n\)个整数\(a_1,a_2,\dots,a_n,0\le ai\le n\),以及\(n\)个整数\(w_1,w_2,\dots,w_n\)。

称 \(a_1, a_2, \dots, a_n\)的 一个排列 \(a_{p[1]}, a_{p[2]}, \dots, a{p[n]}\)为 \(a_1, a_2, \dots, a_n\)的一个合法排列,

当且仅当该排列满足:

对于任意 的 \(k\) 和任意的 \(j\),如果 \(j \le k\),那么 \(a_{p[j]}\)不等于 \(p[k]\)。

(换句话说就是:对于任意的 \(k\) 和任意的 \(j\),如果 \(p[k]\)等于 \(ap[j]\),那么\(k<j\)。)

定义这个合法排列的权值为 \(w_{p[1]} + 2w_{p[2]} + \dots + nw_{p[n]}\)。

你需要求出在所有合法排列中的最大权值。如果不存在合法排列,输出\(-1\)。

\(n\le 5\times 10^5\)

考虑转化题意,\(a_i=k\)表示重新排列后\(a_k\)要在\(a_i\)前面,那么连一条\(k->i\)的有向边。

可以发现这样转化之后,只要能在图中选择一个合法的拓扑序,就能形成一个合法排列。

于是图中有环即无解,无环后每个点仅有\(1\)入度,形成了一棵以\(0\)为根的外向树。

问题转化为:给出一棵有点权的树,从根节点出发选择一个树的遍历顺序,第\(i\)个点经过时间为\(t\)时会给答案加上\(tw_i\)的贡献,求最大总贡献。

你可以发现树上序列\(dp\)归并是正确的,时间复杂度为\(O(n^2)\),可以得到\(60\)分。

我们知道如果一个点权值非常小,那么选择父亲后肯定优先选择它;

于是考虑贪心,每次选择一个权值最小的点,将其缩到父亲上并贡献答案,

父亲的权值变成所在节点的平均值。

具体细节可以看代码 or 别的题解...

这份\(set\)的代码不开\(O2\)是过不去的...

#include<bits/stdc++.h>
#define FL "a"
using namespace std;
typedef long long ll;
typedef double dd;
const int N=5e5+20;
const int inf=2147483647;
const int mod=998244353;
const ll INF=1ll<<60;
inline ll read(){
ll data=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
return data*w;
}
inline void file(){
freopen(FL".in","r",stdin);
freopen(FL".out","w",stdout);
} int n,a[N];
int f[N];int find(int x){return f[x]!=-1?f[x]=find(f[x]):x;}
int head[N],nxt[N<<1],to[N<<1],cnt;
inline void add(int u,int v){
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
} int fa[N],sz[N];ll w[N],ans;
struct node{ll val;int id;};
inline bool operator <(node a,node b){
if(a.val*sz[b.id]!=b.val*sz[a.id])return a.val*sz[b.id]<b.val*sz[a.id];
else return a.id<b.id;
}
set<node>S;set<node>::iterator it1,it2;
int main()
{
n=read();memset(f,-1,sizeof(f));
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)w[i]=read();
for(int i=1;i<=n;i++){
if(find(i)==find(a[i]))
return puts("-1"),0;
f[find(i)]=find(a[i]);
add(a[i],i);fa[i]=a[i];
} w[0]=INF;
for(int i=0;i<=n;i++){
sz[i]=1;f[i]=-1;
S.insert((node){w[i],i});
}
while(S.size()!=1){
it1=S.begin();
int u=it1->id,ff=find(fa[u]);
it2=S.find((node){w[ff],ff});
S.erase(it1);S.erase(it2);
ans+=w[u]*sz[ff];
if(ff)w[ff]+=w[u];
sz[ff]+=sz[u];
S.insert((node){w[ff],ff});
f[find(u)]=ff;
}
printf("%lld\n",ans);
return 0;
}

D2T3:道路

dp状态为\(f[i][a][b]\),没什么好说的。

从前的码风...

#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<iomanip>
#include<cstring>
#include<complex>
#include<vector>
#include<cstdio>
#include<string>
#include<bitset>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<set>
#define FILE "a"
#define mp make_pair
#define pb push_back
#define RG register
#define il inline
using namespace std;
typedef unsigned long long ull;
typedef vector<int>VI;
typedef long long ll;
typedef double dd;
const dd eps=1e-10;
const int mod=1e9+7;
const int N=40010;
const dd pi=acos(-1);
il ll read(){
RG ll data=0,w=1;RG char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
if(ch=='-')w=-1,ch=getchar();
while(ch<='9'&&ch>='0')data=data*10+ch-48,ch=getchar();
return data*w;
}
il void file(){
freopen(FILE".in","r",stdin);
freopen(FILE".out","w",stdout);
}
ll f[105][52][52],s[2][N],a[N],b[N],c[N],n,dfn[N];
il void dfs(int i,int now){
dfn[i]=now;
if(s[0][i])dfs(s[0][i],now+1);
if(s[1][i])dfs(s[1][i],now+2);
if(s[0][i]&&s[1][i])
for(RG int j=0;j<=40;j++)
for(RG int k=0;j+k<=40;k++)
f[dfn[i]][j][k]=min(f[dfn[s[0][i]]][j+1][k]+f[dfn[s[1][i]]][j][k],f[dfn[s[0][i]]][j][k]+f[dfn[s[1][i]]][j][k+1]);
else
for(RG int j=0;j<=40;j++)
for(RG int k=0;j+k<=40;k++)
f[dfn[i]][j][k]=1ll*c[i]*(a[i]+j)*(b[i]+k);
}
int main()
{
n=read();
for(RG int i=1;i<n;i++){
s[0][i]=read();if(s[0][i]<0)s[0][i]=-s[0][i]+n-1;
s[1][i]=read();if(s[1][i]<0)s[1][i]=-s[1][i]+n-1;
}
for(RG int i=n;i<=2*n-1;i++)a[i]=read(),b[i]=read(),c[i]=read();
dfs(1,1);
printf("%lld\n",f[dfn[1]][0][0]);
return 0;
}

HNOI/AHOI2018题解的更多相关文章

  1. 【题解】Luogu P4436 [HNOI/AHOI2018]游戏

    原题传送门 \(n^2\)过百万在HNOI/AHOI2018中真的成功了qwqwq 先将没门分格的地方连起来,枚举每一个块,看向左向右最多能走多远,最坏复杂度\(O(n^2)\),但出题人竟然没卡(建 ...

  2. 【LG4437】[HNOI/AHOI2018]排列

    [LG4437][HNOI/AHOI2018]排列 题面 洛谷 题解 题面里这个毒瘤的东西我们转化一下: 对于\(\forall k,j\),若\(p_k=a_{p_j}\),则\(k<j\). ...

  3. [Bzoj5285][洛谷P4424][HNOI/AHOI2018]寻宝游戏(bitset)

    P4424 [HNOI/AHOI2018]寻宝游戏 某大学每年都会有一次Mystery Hunt的活动,玩家需要根据设置的线索解谜,找到宝藏的位置,前一年获胜的队伍可以获得这一年出题的机会. 作为新生 ...

  4. [HNOI/AHOI2018]转盘(线段树优化单调)

    gugu  bz lei了lei了,事独流体毒瘤题 一句话题意:任选一个点开始,每个时刻向前走一步或者站着不动 问实现每一个点都在$T_i$之后被访问到的最短时间 Step 1 该题可证: 最优方案必 ...

  5. BZOJ5288 & 洛谷4436 & LOJ2508:[HNOI/AHOI2018]游戏——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=5288 https://www.luogu.org/problemnew/show/P4436 ht ...

  6. BZOJ5285 & 洛谷4424 & UOJ384:[HNOI/AHOI2018]寻宝游戏——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=5285 https://www.luogu.org/problemnew/show/P4424 ht ...

  7. 【题解】Luogu P4438 [HNOI/AHOI2018]道路

    原题传送门 实际就是一道简单的树形dp 设f[u][i][j]表示从根结点到结点u经过i条未翻修公路,j条未翻修铁路的贡献最小值 边界条件:f[leaf][i][j]=(A+i)(B+j)C (题目上 ...

  8. 【题解】 [HNOI/AHOI2018]道路 (动态规划)

    懒得复制,戳我戳我 Solution: \(dp[i][j][k]\)以\(i\)为子树根节点,到根节点中有\(j\)条公路没修,\(k\)条铁路没修,存子树不便利和 \(dp[i][j][k]=mi ...

  9. BZOJ5290 & 洛谷4438:[HNOI/AHOI2018]道路——题解

    https://www.lydsy.com/JudgeOnline/problem.php?id=5290 https://www.luogu.org/problemnew/show/P4438 的确 ...

随机推荐

  1. CentOS 7.X下 -- 配置nginx正向代理支持https

    环境说明: 本次测试使用的操作系统为:CentOS 7.2 x86 64位 最小化安装的操作系统,系统基础优化请参考:https://www.cnblogs.com/hei-ma/p/9506623. ...

  2. 2.5 Oracle之存储过程和MERGE INTO语句

    一.MERGE INTO语句 1.merge into语句的功能:我们操作数据库的时候,有时候会遇到insert或者Update这种需求.我们操纵代码时至少需要写一个插入语句和更新语句并且还得单独写方 ...

  3. 关于spring boot 使用 mybatis plus INSERT的时候id报错

    mybatis plus 在INSERT的时候会默认自动设置插入id 我当时数据库采用的id自增. 在使用插入语句的时候并没有set  ID 但是它默认给了一大串 更改mybatis plus全局配置 ...

  4. golang--性能测试和分析

    前言 测试分为:压力测试.负载测试.性能测试,功能测试等等,其中在开发过程中开发人员经常要写一些test case unit 自己的模块进行功能测试测和性能.在分析出模块的性能瓶颈后开发人员就需要针对 ...

  5. [朴孝敏][Sketch]

    歌词来源:http://music.163.com/#/song?id=406907303 作曲 : Ryan S. Jhun/August Rigo/Denzil Remedios [作曲 : Ry ...

  6. runlevel 命令详解

    基础命令学习目录首页 原文链接:https://blog.csdn.net/PecoVio/article/details/82428883 runlevel 知识扩展 linux操作系统自从开始启动 ...

  7. Notes of Daily Scrum Meeting(11.8)

    Notes of Daily Scrum Meeting(11.8) 预备中开始写代码的第一天,因为大家对Android编程的熟悉程度还是不够,所以工程进行的非常缓慢,有四名队员 开始编写自己的任务, ...

  8. servlet的方法解析

    一般来说servlet继承了HttpServlet,我们可以覆盖某些方法来实现自己的功能. Init()和Init(ServletConfig config),我们一般只需覆盖后者,因为这个可以从se ...

  9. arcgis for android apk太大

    原来大概都要20多M, 太大的原来是.so文件 arcgis for android api里面有armeabi armeabi-v7a  x86的 每个so都接近10m 要是都保留就20多m了 由于 ...

  10. Internet History, Technology and Security (Week3)

    Week3. Welcome to week 3! This is our fourth and final week of History where we make the connection ...