BJOI2018簡要題解

BJOI2018簡要題解

D1T1 二進制

題意

pupil 發現對於一個十進制數,不管怎麼將其的數字從新排列,均不影響其是否是 \(3\) 的倍數。他想研究對於二進制,是否也有相似的性質。
因而他生成了一個長爲 \(n\) 的二進制串,但願你對於這個二進制串的一個子區間,能求出其有多少位置不一樣的連續子串,知足在從新排列後(可包含前導 \(0\))是一個 \(3\) 的倍數。兩個位置不一樣的子區間指開始位置不一樣或結束位置不一樣。
因爲他想嘗試儘可能多的狀況,他有時會修改串中的一個位置,而且會進行屢次詢問。ios

對於 \(20\%\) 的數據,\(1 \leq n,m \leq 100\)函數

對於 \(50\%\) 的數據,\(1 \leq n,m \leq 5000\)spa

對於 \(100\%\) 的數據,\(1 \leq n,m \leq 100000\)\(l \leq r\)設計

題解

打表後考慮不合法的區間:code

  • 有奇數個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 的染色方案使得沒有兩個有邊相連的點被染了相同的顏色。ci

如今 pupil 想知道,是否不管 master 的顏色集合是什麼,他均有辦法按照要求染色。get

對於 \(10\%\) 的數據,\(1 \leq n \leq 3\)string

對於 \(20\%\) 的數據,\(1 \leq n \leq 6\)it

對於 \(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出得很好啊。可是本身只能寫出來D1T1T三、D2T3,並且T1還不必定能調對。

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

相關文章
相關標籤/搜索