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加油啊!