BJOI2018简要题解

Posted xzyxzy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BJOI2018简要题解相关的知识,希望对你有一定的参考价值。

BJOI2018简要题解

D1T1 二进制

题意

pupil 发现对于一个十进制数,无论怎么将其的数字重新排列,均不影响其是不是 (3) 的倍数。他想研究对于二进制,是否也有类似的性质。
于是他生成了一个长为 (n) 的二进制串,希望你对于这个二进制串的一个子区间,能求出其有多少位置不同的连续子串,满足在重新排列后(可包含前导 (0))是一个 (3) 的倍数。两个位置不同的子区间指开始位置不同或结束位置不同。
由于他想尝试尽量多的情况,他有时会修改串中的一个位置,并且会进行多次询问。

对于 (20\%) 的数据,(1 leq n,m leq 100)

对于 (50\%) 的数据,(1 leq n,m leq 5000)

对于 (100\%) 的数据,(1 leq n,m leq 100000)(l leq r)

题解

打表后考虑不合法的区间:

  • 有奇数个1且0的个数少于2
  • 只有1个1

由于太久前写的了还写了好久,用set乱搞就好了(我也忘记怎么搞的了,写了好长调了好久现在看不懂了QAQ)

代码

#include<iostream>
#include<algorithm>
#include<set>
#define ll long long
using namespace std;
const int N=1e5+10;
int n,m,a[N];
ll val[N],cal2[N],t[N],tot[N],t2[N],val2[N];
set<int> s,s1;
int read() {int x;scanf("%d",&x);return x;}
ll calc(int L,int R) {return 1ll*((L+1)/2)*((R+2)/2)+1ll*((L+2)/2)*((R+1)/2);}
ll calc2(int L,int R)
{
    ll res=1ll*(L+1)*(R+1)-1;
    if(L) res--;if(R) res--;
    return max(0ll,res);
}
void add1(int x,ll k) {while(x<=n) t[x]+=k,x+=x&(-x);}
ll query1(int x) {ll s=0;while(x) s+=t[x],x-=x&(-x);return s;}
void add2(int x,ll k) {while(x<=n) t2[x]+=k,x+=x&(-x);}
ll query2(int x) {ll s=0;while(x) s+=t2[x],x-=x&(-x);return s;}
void WorkQ()
{
    int l,r,len;ll ans=0;
    scanf("%d%d",&l,&r);len=r-l+1;
    int pl=*s.lower_bound(l);
    int pr=*(--s.upper_bound(r));
    if(pl>r) return (void)printf("%lld
",tot[len]-cal2[r-l+1]);
    if(pl==pr) return (void)printf("%lld
",tot[len]-calc(pl-l,r-pl)-cal2[pl-l]-cal2[r-pr]);
    ans=query1(pr-1)-query1(pl);
    int d1=*s.upper_bound(pl)-pl-1;ans+=calc(pl-l,d1)+cal2[pl-l];
    int d2=pr-*(--s.lower_bound(pr))-1;ans+=calc(d2,r-pr)+cal2[r-pr];
    pl=*s1.lower_bound(l);
    pr=*(--s1.upper_bound(r));
    if(pl==pr) ans+=calc2(pl-l,r-pr);
    if(pl<pr)
    {
        d1=*s1.upper_bound(pl)-pl-1;ans+=calc2(pl-l,d1);
        d2=pr-*(--s1.lower_bound(pr))-1;ans+=calc2(d2,r-pr);
        ans+=query2(pr-1)-query2(pl);
    }
    printf("%lld
",tot[len]-ans);
}
void WorkM()
{
    int p=read();a[p]^=1;
    if(a[p]==0)//1->0
    {
        s.insert(p);s1.erase(p);
        set<int>::iterator fr,nt,ffr,nnt;
        fr=s.lower_bound(p);fr--;
        nt=s.upper_bound(p);
        add1(p,-val[p]);
        add1(p,val[p]=calc(p-*fr-1,*nt-p-1));
        if(*fr!=0)
        {
            ffr=fr;ffr--;
            add1(*fr,-val[*fr]);
            add1(*fr,val[*fr]=calc(*fr-*ffr-1,p-*fr-1));
        }
        if(*nt!=n+1)
        {
            nnt=nt;nnt++;
            add1(*nt,-val[*nt]);
            add1(*nt,val[*nt]=calc(*nt-p-1,*nnt-*nt-1));
        }
        if(*nt-p-1>0)
        {
            add1(*nt-1,-val[*nt-1]);
            add1(*nt-1,val[*nt-1]=cal2[*nt-p-1]);
        }
        if(p-*fr-1>0)
        {
            add1(p-1,-val[p-1]);
            add1(p-1,val[p-1]=cal2[p-*fr-1]);
        }
        fr=s1.lower_bound(p);fr--;
        nt=s1.upper_bound(p);
        add2(p,-val2[p]),val2[p]=0;
        if(*fr!=0)
        {
            ffr=fr;ffr--;
            add2(*fr,-val2[*fr]);
            add2(*fr,val2[*fr]=calc2(*fr-*ffr-1,*nt-*fr-1));
        }
        if(*nt!=n+1)
        {
            nnt=nt;nnt++;
            add2(*nt,-val2[*nt]);
            add2(*nt,val2[*nt]=calc2(*nt-*fr-1,*nnt-*nt-1));
        }
    }
    else//0->1
    {
        s.erase(p);s1.insert(p);
        set<int>::iterator fr,nt,ffr,nnt;
        fr=s.upper_bound(p);nt=fr;fr--;
        if(*fr!=0)
        {           
            ffr=fr;ffr--;
            add1(*fr,-val[*fr]);
            add1(*fr,val[*fr]=calc(*fr-*ffr-1,*nt-*fr-1));
        }
        if(*nt!=n+1)
        {
            nnt=nt;nnt++;
            add1(*nt,-val[*nt]);
            add1(*nt,val[*nt]=calc(*nt-*fr-1,*nnt-*nt-1));
        }       
        if(*nt-p-1>0) add1(*nt-1,-val[*nt-1]),val[*nt-1]=0;
        if(p-*fr-1>0) add1(p-1,-val[p-1]),val[p-1]=0;
        add1(p,-val[p]),val[p]=0;
        add1(*nt-1,val[*nt-1]=cal2[*nt-*fr-1]);
        fr=s1.lower_bound(p);fr--;
        nt=s1.upper_bound(p);
        add2(p,-val2[p]);
        add2(p,val2[p]=calc2(p-*fr-1,*nt-p-1));
        if(*fr!=0)
        {
            ffr=fr;ffr--;
            add2(*fr,-val2[*fr]);
            add2(*fr,val2[*fr]=calc2(*fr-*ffr-1,p-*fr-1));
        }
        if(*nt!=n+1)
        {
            nnt=nt;nnt++;
            add2(*nt,-val2[*nt]);
            add2(*nt,val2[*nt]=calc2(*nt-p-1,*nnt-*nt-1));
        }
    }
}
int main()
{
    cin>>n;
    s.insert(0);s.insert(n+1);s1.insert(0);s1.insert(n+1);
    for(int i=1;i<=n;i++) a[i]=read(),a[i]?s1.insert(i):s.insert(i);
    for(int i=1;i<=n;i++) cal2[i]=(i&1)?(i+1)/2:cal2[i-1];
    for(int i=1;i<=n;i++) cal2[i]+=cal2[i-1];
    for(int i=1;i<=n;i++) tot[i]=tot[i-1]+i;
    set<int>::iterator it=s.begin(),fr,nt;
    for(;it!=s.end();it++)
    {
        if(*it==n+1)
        {
            fr=it,fr--;
            if(*it-*fr-1>0) add1(*it-1,val[*it-1]=cal2[*it-*fr-1]);
        }
        if(*it==0||*it==n+1) continue;
        fr=nt=it;fr--,nt++;
        add1(*it,val[*it]=calc(*it-*fr-1,*nt-*it-1));
        if(*it-*fr-1>0) add1(*it-1,val[*it-1]=cal2[*it-*fr-1]);
    }
    it=s1.begin();
    for(;it!=s1.end();it++)
    {
        if(*it==0||*it==n+1) continue;
        fr=nt=it;fr--,nt++;
        add2(*it,val2[*it]=calc2(*it-*fr-1,*nt-*it-1));
    }
    for(cin>>m;m;m--) read()==1?WorkM():WorkQ();
}

D1T2 染色

题意

pupil 喜欢给图的顶点染颜色。有一天,master 想刁难他,于是给了他一个无重边和自环的无向图,
并且对每个点分别给了一个大小为 (2) 的颜色集合,pupil 只能从这个集合中选一种颜色给这个点染色。master 希望 pupil 的染色方案使得没有两个有边相连的点被染了相同的颜色。

现在 pupil 想知道,是否无论 master 的颜色集合是什么,他均有办法按照要求染色。

对于 (10\%) 的数据,(1 leq n leq 3)

对于 (20\%) 的数据,(1 leq n leq 6)

对于 (50\%) 的数据,(1 leq n leq 1000)(0 leq m leq 2000)

对于 (100\%) 的数据,(1 leq n leq 10000)(0 leq m leq 20000)(1 leq T leq 10)

题解

毫无思路。

分以下几种情况讨论:

  • 存在奇环:直接所有点{A,B},NO。

  • 存在两个分离的环:NO。

    对于一个大小为4的环,构造{A,C},{B,C},{A,B},那么剩下那个{A,X}就一定只能选X。

    两个分离的环就这样构造,使得在连接两个环的路径上产生冲突即可。

判完上述情况后,对于每个联通块这样考虑:

  • m<=n:YES。

  • m>=n+2:NO,一定存在两个分离的环。

  • m=n+1:依次删掉所有的叶子,最后剩下的一定是两个点之间有三条路径。

    可以证明,这三条路径有两条是2的时候YES,否则NO。

    ?

    对于三条路径都是奇数的情况:

    选最短的一条填{A,B},则这两点不同。

    选一条填{A,C}{B,C},另一条填{B,C},{A,C},所以开头无论填A还是B,都可以通过这两条路径限制结尾不能填A和B。

    ?

    对于三条路径都是偶数的情况:

    选最短的一条填{A,B},则这两点相同。

    另外两条填{A,C}{C,B}{B,A}/{B,C}{C,A}{A,B},同上可以限制结尾不能填A和B。

    但是另外两条路基如果有一条长度为2,则构造不出。

    ?

    综上,证明完毕。

代码

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int N=4e4+10;
struct edge{int next,to;}a[N];
int n,m,head[N],cnt,del[N],col[N],flag,tt,sn,sm,p1,p2,du[N];
queue<int> Q;
void link(int x,int y) {a[++cnt]=(edge){head[x],y};head[x]=cnt;}
void dfs(int x,int c)
{
    col[x]=c;sn++;if(flag) return;
    int son=0;
    for(int i=head[x];i;i=a[i].next)
    {
        int R=a[i].to;if(del[R]) continue;
        sm++;son++;
        if(col[R]&&col[R]!=3-c) {flag=1;return;}
        if(!col[R]) dfs(R,3-c);
    }
    if(son==3) p1?p2=x:p1=x;
}
void calc(int x,int fr,int s)
{
    if(x==p2) {tt+=(s==2);return;}
    for(int i=head[x];i;i=a[i].next)
        if(!del[a[i].to]&&a[i].to!=fr)
            calc(a[i].to,x,s+1);
}
void Work()
{NO
    memset(du,0,sizeof(du));
    memset(head,0,sizeof(head));
    memset(col,0,sizeof(col));
    memset(del,0,sizeof(del));
    flag=0;cnt=0;
    cin>>n>>m;
    for(int i=1,x,y;i<=m;i++)
        scanf("%d%d",&x,&y),du[x]++,du[y]++,link(x,y),link(y,x);
    for(int i=1;i<=n;i++) if(du[i]==1) Q.push(i);
    while(!Q.empty())
    {
        int x=Q.front();Q.pop();del[x]=1;
        for(int i=head[x];i;i=a[i].next)
            if(--du[a[i].to]==1) Q.push(a[i].to);
    }
    for(int i=1;i<=n;i++)
        if(!col[i]&&!del[i])
        {
            sn=sm=p1=p2=0;dfs(i,1);sm/=2;
            if(sm>=sn+2||flag) return (void)puts("NO");
            if(sm==sn+1) {calc(p1,0,tt=0);if(tt<2) return (void)puts("NO");}
        }
    puts("YES");
}
int main() {int T;cin>>T;while(T--) Work();}

D1T3 求和

题意

master 对树上的求和非常感兴趣。他生成了一棵有根树,并且希望多次询问这棵树上一段路径上所有节点深度的 (k) 次方和,而且每次的 (k) 可能是不同的。此处节点深度的定义是这个节点到根的路径上的边数。
他把这个问题交给了 pupil,但 pupil 并不会这么复杂的操作,你能帮他解决吗?

对于 (30\%) 的数据,(1 leq n,m leq 100)

对于 (60\%) 的数据,(1 leq n,m leq 1000)

对于 (100\%) 的数据,(1 leq n,m leq 300000,1 leq k leq 50)

题解

送分题。

代码

#include<iostream>
using namespace std;
const int N=3e5+10,mod=998244353;
struct edge{int next,to;}a[N<<1];
int n,q,fa[20][N],cnt,head[N],dep[N],val[N][51];
void link(int x,int y) {a[++cnt]=(edge){head[x],y};head[x]=cnt;}
void dfs(int x,int fr)
{
    dep[x]=dep[fr]+1;val[x][0]=1;
    for(int i=head[x];i;i=a[i].next)
        if(a[i].to!=fr) fa[0][a[i].to]=x,dfs(a[i].to,x);
}
void sum(int x,int fr)
{
    for(int i=1;i<=50;i++) (val[x][i]+=val[fr][i])%=mod;
    for(int i=head[x];i;i=a[i].next) if(a[i].to!=fr) sum(a[i].to,x);
}
int main()
{
    cin>>n;
    for(int i=1,x,y;i<n;i++)
        scanf("%d%d",&x,&y),link(x,y),link(y,x);
    dep[0]=-1;dfs(1,0);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=50;j++)
            val[i][j]=1ll*val[i][j-1]*dep[i]%mod;
    sum(1,0);
    for(int p=1;p<=18;p++)
        for(int i=1;i<=n;i++)
            fa[p][i]=fa[p-1][fa[p-1][i]];
    for(cin>>q;q;q--)
    {
        int x,y,k,xx,yy,lca,ans;
        scanf("%d%d%d",&x,&y,&k);xx=x,yy=y;
        if(dep[x]<dep[y]) swap(x,y);
        for(int p=18;p>=0;p--)
            if(dep[fa[p][x]]>=dep[y]) x=fa[p][x];
        for(int p=18;p>=0;p--)
            if(fa[p][x]!=fa[p][y])
                x=fa[p][x],y=fa[p][y];
        lca=(x==y?x:fa[0][x]);
        ans=((val[xx][k]+val[yy][k])%mod-(val[lca][k]+val[fa[0][lca]][k])%mod)%mod;
        printf("%d
",(ans+mod)%mod);
    }
}

D2T1 双人猜数游戏

题意

Alice 和 Bob 是一对非常聪明的人,他们可以算出各种各样游戏的最优策略。现在有个综艺节目《最强大佬》请他们来玩一个游戏。主持人写了三个正整数 (s)(m)(n) ,然后一起告诉 Alice 和 Bob (s leq m leq n) 以及 (s) 是多少。(即,(s) 是接 下来要猜的 (m)(n) 的下限。)之后主持人单独告诉 Alice (m)(n) 的乘积是多少, 单独告诉 Bob (m)(n) 的和是多少。

当然,如果一个人同时知道 (m)(n) 的乘积以及 (m)(n) 的和话就能很容易地算出 (m)(n) 分别是多少,但现在 Alice 和 Bob 只分别知道其中一个,而且只分别知道其中一个,而且他们只能回答主持人的问题,不能交流。从 Alice 或 Bob(见输入)开始 依次询问 Alice/Bob 知不知道 (m)
(n) 分别是多少, Alice/Bob 只能回答知道/不知道。

为了节目效果,为了显示出 Alice 和 Bob 非常聪明,主持人希望 Alice 和 Bob 一共说了 (t) 次“不知道 ”以后两个人都知道 (m)(n) 是多少了 。现在主持人找到你,希望让帮他构造一组符合条件的 (m)(n)

对于 (40\%) 的数据, (t = 2)

对于 (100\%) 的数据, (1 leq s leq 200)(2 leq t leq 15),输入数据保证有解。

题解

神仙提交答案题。

他们的思维方式见:https://www.luogu.org/problemnew/solution/P4459

然后就可以设计(dp[i][j][k])表示两个数字分别为(i,j),进行了(k)轮,是否已经确定了。

然后按照两人的思维方式进行转移。有点毒瘤啊。

代码

一秒之内可以跑出一个点。

#include<iostream>
#include<cmath>
using namespace std;
const int N=300;
int f[N+1][N+1][20];
int s,t;
string S;
int calc1(int x,int y,int k)
{
    int num=x*y,up=sqrt(x*y),xx=0,yy=0,cnt=0;
    for(int i=s;i<=up;i++)
        if(num%i==0&&(!k||!f[i][num/i][k-1]))
            xx=i,yy=num/i,cnt++;
    return cnt==1&&xx==x&&yy==y;
}
int calc2(int x,int y,int k)
{
    int num=x+y,up=num/2,xx=0,yy=0,cnt=0;
    for(int i=s;i<=up;i++)
        if(!k||!f[i][num-i][k-1])
            xx=i,yy=num-i,cnt++;
    return cnt==1&&xx==x&&yy==y;
}
int calc3(int x,int y)
{
    int num=x*y,up=sqrt(x*y),xx=0,yy=0,cnt=0;
    for(int i=s;i<=up;i++)
        if(num%i==0&&f[i][num/i][t]&&(t<2||!f[i][num/i][t-2]))
            xx=i,yy=num/i,cnt++;
    return cnt==1&&xx==x&&yy==y;
}
int calc4(int x,int y)
{
    int num=x+y,up=num/2,xx=0,yy=0,cnt=0;
    for(int i=s;i<=up;i++)
        if(f[i][num-i][t]&&(t<2||!f[i][num-i][t-2]))
            xx=i,yy=num-i,cnt++;
    return cnt==1&&xx==x&&yy==y;
}
void Work(int x,int y)
{
    if(!f[x][y][t]) return;
    for(int k=0;k<t;k++) if(f[x][y][k]) return;
    int nw=((t&1)&&S[0]=='A')||(!(t&1)&&S[0]=='B');
    int fl=nw?calc3(x,y):calc4(x,y);
    if(fl) printf("%d %d
",x,y),exit(0);
}
int main()
{
/*  
    freopen("makeout.in","r",stdin);
    string fin;cin>>fin;
    freopen(("guess"+fin+".in").c_str(),"r",stdin);
    freopen(("guess"+fin+".out").c_str(),"w",stdout);
*/
    cin>>s>>S>>t;
    for(int k=0,nw=S[0]=='A';k<=t;k++,nw^=1)
        for(int i=s;i<=N;i++)
            for(int j=i;j<=N;j++)
            {
                if(k>1) f[i][j][k]=f[i][j][k-2];
                f[i][j][k]|=nw?calc1(i,j,k):calc2(i,j,k);
            }
    for(int sum=2*s;;sum++)
        for(int i=s;i<=sum/2;i++)
            Work(i,sum-i);
}

D2T2 链上二次求和

题意

有一条长度为 (n) 的链( (forall 1 leq i < n) ,点 (i) 与点 (i+1) 之间有一条边的无向图), 每个点有一个整数权值,第 (i) 个点的权值是 (a_i) 。现在有 (m) 个操作,每个操作如下:

操作 1(修改):给定链上两个节点 (u)(v) 和一个整数 (d),表示将链上 (u)(v) 唯一的简单路径上每个点权值都加上 (d)

操作 2(询问):给定两个正整数 (l)(r),表示求链上所有节点个数大于等于 (l) 且小于等于 (r) 的简单路径节点权值和之和。由于答案很大,只用输出对质数 (1000000007) 取模的结果即可。

一条节点个数为 (k) 的简单路径节点权值和为这条上所有 (k) 个节点(包括端点)的权值之和,而本题中要求是对所有满足要求的简单路径,求这一权值和的和。

由于是无向图,路径也是无向的,即点 (1) 到点 (2) 的路径与点 (2) 到点 (1) 的路径是同一条,不要重复计算。

记操作 1(修改)的次数为 (m^prime)

对于全部数据, 保证 (n leq 200000, m leq 500000, m^prime leq 100000, 0 leq a_i < 1000000007)

(1 leq u leq n, 1leq v leq n, 0 leq d < 1000000007, l leq r leq n)

题解

设S为权值前缀和,答案为[sum_{i=l}^{r}sum_{j=i}^n(S_j-S_{j-i})=sum_{i=l}^{r}(sum_{j=i}^{n}S_j-sum_{j=0}^{n-i}S_j)=sum_{i=l}^{r}(SS_n-SS_{i-1}-SS_{n-i})],其中SS为S的前缀和。

所以就是用线段树动态维护二维前缀和。

考虑加[l,r]对二维前缀和造成的影响:

  • (lle ile r)(+=frac{(i-l+1)(i-l+2)}{2})
  • (r< i),+=(frac{(r-l+1)(r-l+2)}{2}+(r-i)(r-l+1))

所以维护二次函数就好了。

代码

#include<iostream>
using namespace std;
const int N=8e5+10,mod=1e9+7,inv2=500000004,inv6=166666668;
int n,m,a[N],b[N],c[N],t[N];
int S(int n) {return 1ll*n*(n+1)%mod*(2*n+1)%mod*inv6%mod;}
void add(int &x,int y) {x+=y;if(x>=mod) x-=mod;}
void put(int x,int l,int r,int A,int B,int C)
{
    int s0=r-l+1,s1=1ll*(l+r)*(r-l+1)/2%mod;
    int s2=(S(r)-S(l-1)+mod)%mod;
    add(t[x],1ll*A*s2%mod);
    add(t[x],1ll*B*s1%mod);
    add(t[x],1ll*C*s0%mod);
    add(a[x],A);add(b[x],B);add(c[x],C);
}
void pushdown(int x,int l,int r)
{
    if(!(a[x]+b[x]+c[x])) return;
    int mid=(l+r)>>1;
    put(x<<1,l,mid,a[x],b[x],c[x]);
    put(x<<1|1,mid+1,r,a[x],b[x],c[x]);
    a[x]=b[x]=c[x]=0;
}
void Add(int x,int l,int r,int gl,int gr,int a,int b,int c)
{
    if(l>=gl&&r<=gr) return (void)put(x,l,r,a,b,c);
    int mid=(l+r)>>1;
    pushdown(x,l,r);
    if(gl<=mid) Add(x<<1,l,mid,gl,gr,a,b,c);
    if(gr>mid) Add(x<<1|1,mid+1,r,gl,gr,a,b,c);
    t[x]=(t[x<<1]+t[x<<1|1])%mod;
}
int Query(int x,int l,int r,int gl,int gr)
{
    if(l>=gl&&r<=gr) return t[x];
    int mid=(l+r)>>1,res=0;
    pushdown(x,l,r);
    if(gl<=mid) res+=Query(x<<1,l,mid,gl,gr);
    if(gr>mid) res+=Query(x<<1|1,mid+1,r,gl,gr);
    return res%mod;
}
int main()
{
    cin>>n>>m;
    for(int i=1,x,s=0,ss=0;i<=n;i++)
        scanf("%d",&x),add(s,x),add(ss,s),Add(1,0,n,i,i,0,0,ss);
    for(int i=1;i<=m;i++)
    {
        int op,l,r,v;scanf("%d%d%d",&op,&l,&r);
        if(l>r) swap(l,r);
        if(op==1)
        {
            scanf("%d",&v);v=1ll*v*inv2%mod;
            int a=v,b=(1ll*(3-2*l)*v%mod+mod)%mod;
            int c=(1ll*v*(1ll*l*l%mod-3ll*l+2)%mod+mod)%mod;
            Add(1,0,n,l,r,a,b,c);
            a=0;b=2ll*(r-l+1)*v%mod;
            c=(1ll*(r-l+1)*(r-l+2)%mod*v%mod-1ll*r*b%mod+mod)%mod;
            if(r!=n) Add(1,0,n,r+1,n,a,b,c);
        }
        else
        {
            int ans=1ll*Query(1,0,n,n,n)*(r-l+1)%mod;
            add(ans,mod-Query(1,0,n,l-1,r-1));
            add(ans,mod-Query(1,0,n,n-r,n-l));
            printf("%d
",ans);
        }
    }
}

D2T3 治疗之雨

题意

(没玩过《炉石传说》的人可以跳过这一段)今天我们来探讨下《炉石传说》中“治疗之雨”(恢复 (12) 点生命值,随机分配到所有友方角色上)和“暗影打击装甲”(每当一个角色获得治疗,便对随机敌人造成 (1) 点伤害)这两张卡牌之间的互动效果。假设你场上有 (m) 个剩余生命值无限大且生命值上限减去剩余生命值也无限大的随从,而对方的场上有 (k) 个暗影打击装甲,你的英雄剩余生命值为 (p) 、生命值上限为 (n) ,现在你使用了一张可以恢复无限多(而不是 (12) 点)生命值的治疗之雨,问治疗之雨期望总共恢复了几点生命值以后你的英雄会死亡(生命值降为 (0) ;治疗之雨的判定机制使得在此后再也不会为英雄恢复生命值)。

注:题目背景与题目描述有冲突的地方请以题目描述为准

下面让我们再形式化地描述一遍问题。

你现在有 (m+1) 个数:第一个为 (p) ,最小值为 (0) ,最大值为 (n) ;剩下 (m) 个都是无穷,没有最小值或最大值。你可以进行任意多轮操作,每轮操作如下:

在不为最大值的数中等概率随机选择一个(如果没有则不操作),把它加一;

进行 (k) 次这个步骤:在不为最小值的数中等概率随机选择一个(如果没有则不操作),把它减一。

现在问期望进行多少轮操作以后第一个数会变为最小值 (0)

对于 (10\%) 的数据, (n leq 3)(m, k leq 2)

对于 (20\%) 的数据, (n, m, k leq 5)

对于 (30\%) 的数据, (n, m, k leq 30)

对于 (40\%) 的数据, (n, m, k leq 50)

对于 (50\%) 的数据, (n, m, k leq 200)

对于 (70\%) 的数据, (n leq 200)

对于 (80\%) 的数据, (n leq 500)

对于 (100\%) 的数据, (1 leq T leq 100)(1 leq p leq n leq 1500)(0 leq m, k leq 1000000000)

//保证不存在 (n=p=k=1)(m=0) 的情况(因为出题人判错了)

//保证不存在答案的分母是(1000000007)的倍数的情况(因为出题人没想到)

题解

这题应该能自己想出来的,但是没有看懂题所以通过题解看懂了题目。。

题意是每次给没有满血的位置+1,给没有死的位置-1。

那么就可以高斯消元了。由于其矩阵的优美性质,可以做到(n^2)

代码

#include<iostream>
#include<cstring>
using namespace std;
const int N=1600,mod=1e9+7;
int n,m,p,k,rv[N],P[N],F[N][N],f[N];
int ksm(int x,int k)
{
    int s=1;for(;k;k>>=1,x=1ll*x*x%mod)
                if(k&1) s=1ll*s*x%mod;return s;
}
int Work()
{
    memset(P,0,sizeof(P));
    cin>>n>>p>>m>>k;
    if(!k||(m==0&&k==1)) return -1;
    if(m==0) {int res=0;for(;p>0;p-=k,res++) if(p<n) p++;return res;}
    int rev=ksm(m+1,mod-2);rv[1]=1;
    for(int i=2;i<=n+1;i++) rv[i]=mod-1ll*mod/i*rv[mod%i]%mod;
    for(int i=0,C=1;i<=min(n,k);C=1ll*C*rv[i+1]%mod*(k-i)%mod,i++)
        P[i]=1ll*C*ksm(rev,i)%mod*ksm(1ll*m*rev%mod,k-i)%mod;
    for(int i=1;i<n;i++)
    {
        for(int j=1;j<=i;j++)
            F[i][j]=(1ll*m*rev%mod*P[i-j]%mod+1ll*rev*P[i-j+1]%mod)%mod;
        F[i][i+1]=1ll*rev*P[0]%mod;
        (F[i][i]+=mod-1)%=mod;
        F[i][n+1]=mod-1;
    }
    for(int i=1;i<=n;i++) F[n][i]=P[n-i];
    (F[n][n]+=mod-1)%=mod;F[n][n+1]=mod-1;

    for(int i=n;i>=2;i--)
    {
        if(!F[i][i]) return -1;
        int t=1ll*F[i-1][i]*ksm(F[i][i],mod-2)%mod;
        for(int j=i;j>=1;j--) F[i-1][j]=(F[i-1][j]-1ll*F[i][j]*t%mod+mod)%mod;
        F[i-1][n+1]=(F[i-1][n+1]-1ll*F[i][n+1]*t%mod+mod)%mod;
    }
    for(int i=1;i<=n;i++)
    {
        int res=F[i][n+1];
        for(int j=1;j<i;j++) res=(res-1ll*f[j]*F[i][j]%mod+mod)%mod;
        f[i]=1ll*res*ksm(F[i][i],mod-2)%mod;
    }
    return f[p];
}
int main() {int T;cin>>T;while(T--) printf("%d
",Work());}

后记

这一年的BJOI出得很好啊。但是自己只能写出来D1T1T3、D2T3,而且T1还不一定能调对。

菜是原罪啊。。。HNOI2019加油啊!

以上是关于BJOI2018简要题解的主要内容,如果未能解决你的问题,请参考以下文章

[BJOI2018]治疗之雨

JXOI2018简要题解

HAOI2018 简要题解

题解 [BJOI2017]开车

HAOI2018 简要题解

「BJOI2018」求和