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\n",tot[len]-cal2[r-l+1]);
    if(pl==pr) return (void)printf("%lld\n",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\n",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\n",(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\n",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, 1\leq 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]对二维前缀和造成的影响:

  • \(l\le i\le 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\n",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\n",Work());}

后记

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

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

原文地址:https://www.cnblogs.com/xzyxzy/p/10656803.html

时间: 2024-11-04 22:46:14

BJOI2018简要题解的相关文章

AGC025简要题解

AGC025简要题解 B RGB Coloring 一道简单题,枚举即可. C Interval Game 考虑可以进行的操作只有两种,即左拉和右拉,连续进行两次相同的操作是没有用的. 左拉时肯定会选择右端点尽量小的,右拉选择左端点尽量大的,所以排序之后贪心即可. D Choosing Points 首先证明对于所有\(d\),假设让两个不能同时选的点之间连一条边,那么结果是一张二分图. \(d\)是奇数可以黑白染色,\(d\)是偶数的时候,显然连边的两点在同一个颜色内.那么我们可以只考虑这个颜

月考简要题解

模拟赛简要题解 一下题目均可在loj上找到 10178. 「一本通 5.5 例 4」旅行问题 简单题,将n扩大到2 * n,单调队列即可,注意正反向. #include<iostream> #include<cstring> #include<cmath> #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; const int N=2000

JXOI2018简要题解

JXOI2018简要题解 T1 排序问题 题意 九条可怜是一个热爱思考的女孩子. 九条可怜最近正在研究各种排序的性质,她发现了一种很有趣的排序方法: Gobo sort ! Gobo sort 的算法描述大致如下: 假设我们要对一个大小为 \(n\) 的数列 \(a\) 排序. 等概率随机生成一个大小为 \(n\) 的排列 \(p\) . 构造一个大小为 \(n\) 的数列 \(b\) 满足 \(b_i=a_{p_i}\) ,检查 \(b\) 是否有序,如果 \(b\) 已经有序了就结束算法,并

杂题记录及简要题解(三)

以下是大概 5 月初开始做的一些题.以前的简要题解都是骗人的.这次真的是简要题解了(大雾 相对之前改良了一下题目名称的格式. 2017 计蒜之道 初赛 - 腾讯狼人杀 二分答案 \(x\) 后原问题变为检验是否存在选取方案 \((V, E)(|V| = k)\) 使得 \(\sum_\limits{e \in E} w_e - xk \cdot (2n- k)\).式子可以写成 \(\sum_\limits{e \in E} w_e + \frac{k(k - 1)}{2} \cdot 2x -

【简要题解】Hihocoder 重复旋律1-8简要题解

[简要题解]Hihocoder 重复旋律1-8简要题解 编号 名称标签 难度 1403 后缀数组一·重复旋律 Lv.4 1407 后缀数组二·重复旋律2 Lv.4 1415 后缀数组三·重复旋律3 Lv.4 1419 后缀数组四·重复旋律4 Lv.4 1445 后缀自动机二·重复旋律5 Lv.4 1449 后缀自动机三·重复旋律6 Lv.4 1457 后缀自动机四·重复旋律7 Lv.1 1465 后缀自动机五·重复旋律8 Lv.1 1466 后缀自动机六·重复旋律9 Lv.1 后缀数组 思路简单

《信奥一本通》提高版—简要题解

<信奥一本通>提高版-简要题解 贪心 活动安排: 按右端点排序,因为越早结束越好. 然后从1扫到n,每次比较当前位置线段的左端点是否大于上一个选的线段的右端点.如果大于,那么ans++,将上一个选的线段的右端点更新为当前线段的右端点:如果小于,那什么都不用做.因为选上一条一定比选当前这一条更优(结束时间更早). 种树 按右端点排序,对于每个区间的需求,从右端往左端扫,只要没种到树的就种,ans++. 因为要使前面的需求尽量与后面的需求重叠,从而使树的数量最少 喷水装置 观察+画图发现对于一个圆

【题解】CF616(Div 2)简要题解

[题解]CF616(Div 2)简要题解 A 分类讨论 若数码和是奇数 若最后一个数是奇数:找到从前往后第一个奇数数位删掉 若最后一个数是偶数:不断删除最后一个数直到这个剩下的数是奇数,由于之前删掉的数都是偶数所以对数码和\(\mod 2\)不会有影响.再做一遍第一个算法即可. 若数码和是偶数 若最后一个数是奇数:符合条件 若最后一个数是偶数:不断删除最后一个数直到奇数.由于之前删掉的数都是偶数所以对数码和\(\mod 2\)不会有影响,直接输出即可. 最后要判断一下前导零. B 可以发现若有合

Codeforces Round #483 (Div. 1) 简要题解

来自FallDream的博客,未经允许,请勿转载,谢谢. 为了证明一下我又来更新了,写一篇简要的题解吧. 这场比赛好像有点神奇,E题莫名是道原题,导致有很多选手直接过掉了(Claris 表演24s过题).然而D题比E题要难一些,分还少. A. Finite or not? 先把\(\frac{p}{q}\)约成最简分数,然后就是要判断是否\(q\)的所有质因数都是\(b\)的质因数. 每次取\(g=gcd(b,q)\),并尽可能的让\(q\)除\(g\),最后判断\(q\)是否是1即可. 还有一

弱省胡策系列简要题解

现在不是非常爽,感觉智商掉没了,就整理一下最近弱省胡策的题目吧. 其实题目质量还是很高的. 如果实在看不懂官方题解,说不定这里bb的能给您一些帮助呢? [弱省胡策]Round #0 A 20%数据,O(n4)傻逼dp. 40%数据,O(n3)傻逼dp. 100%数据,令f(x1,y1,x2,y2)表示从(x1,y1)走到(x2,y2)的路径条数.于是所有路径就是f(1,2,n?1,m)×f(2,1,n,m?1).然而两条路径可能在中间的某个点相交,我们找出最早的交点,并在这个交点互换两条路径的后