Day1T1接水果:弄出dfs序可以发现就是要满足在一个一个矩形里面,求覆盖一个点的第k大矩形,扫描线然后整体二分即可
Code(远古代码):
#include <bits/stdc++.h>
#define N 40005
using namespace std;
struct nod
{
int x,y,z,v,w;
nod(int a=0,int b=0,int c=0,int d=0,int e=0){x=a,y=b,z=c,v=d,w=e;}
bool operator<(const nod &a)const{return x<a.x;}
}a[N*3],q[N*2],t[N*2];
int n,m,k,x,y,z,g,he[N],to[N<<1],ne[N<<1],cnt,anc[N][20],dep[N],in[N],ou[N],I,tot,f[N],val[N],vl[N],ans[N];
bool cmp(nod a,nod b){return a.v<b.v;}
void add(int x,int y){to[++cnt]=y,ne[cnt]=he[x],he[x]=cnt;}
void dfs(int x)
{
in[x]=++I;for(int i=1;i<=15;i++) anc[x][i]=anc[anc[x][i-1]][i-1];
for(int i=he[x],y;i;i=ne[i]) if((y=to[i])!=anc[x][0]) anc[y][0]=x,dep[y]=dep[x]+1,dfs(y);
ou[x]=I;
}
int find(int x,int y){for(int i=15;~i;i--) if((1<<i)<=y) x=anc[x][i],y-=(1<<i);return x;}
void upd(int x,int a){for(int i=x;i<=n;i+=i&-i) f[i]+=a;}
int que(int x){int ans=0;for(int i=x;i;i-=i&-i) ans+=f[i];return ans;}
void solve(int b,int e,int l,int r,int L,int R)
{
if(b>e) return;
if(L==R){for(int i=b;i<=e;i++) ans[q[i].z]=L;return;}
int MID=(L+R)>>1,mid=l-1,p=l;
for(int i=b;i<=e;i++) vl[q[i].z]=0;sort(a+l,a+r+1,cmp);
while(mid<r&&a[mid+1].v<=MID) mid++;sort(a+l,a+mid+1);
for(int i=b;i<=e;i++)
{
while(p<=mid&&a[p].x<=q[i].x) upd(a[p].y,a[p].w),upd(a[p].z+1,-a[p].w),p++;
vl[q[i].z]+=que(q[i].y);
}
while(p>l) p--,upd(a[p].y,-a[p].w),upd(a[p].z+1,a[p].w);
for(int i=(p=b);i<=e;i++) if(val[q[i].z]<=vl[q[i].z]) t[p++]=q[i];
for(int i=(p=e);i>=b;i--) if(val[q[i].z]>vl[q[i].z]) t[p--]=q[i];
for(int i=b;i<=e;q[i]=t[i],i++) if(~vl[q[i].z]&&val[q[i].z]>vl[q[i].z]) val[q[i].z]-=vl[q[i].z],vl[q[i].z]=-1;
solve(b,p,l,mid,L,MID),solve(p+1,e,mid+1,r,MID+1,R);
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=2;i<=n;i++) scanf("%d%d",&x,&y),add(x,y),add(y,x);
dfs(1);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
if(dep[x]>dep[y]) swap(x,y);
if(dep[x]<dep[y]&&anc[g=find(y,dep[y]-dep[x]-1)][0]==x)
{
a[++tot]=nod(1,in[y],ou[y],z,1);
a[++tot]=nod(in[g],in[y],ou[y],z,-1);
a[++tot]=nod(ou[g]+1,in[y],ou[y],z,1);
}
else a[++tot]=nod(in[x],in[y],ou[y],z,1),a[++tot]=nod(ou[x]+1,in[y],ou[y],z,-1);
}
for(int i=1;i<=k;i++) scanf("%d%d%d",&x,&y,&val[i]),q[i]=nod(in[x],in[y],i,0,0),q[i+k]=nod(in[y],in[x],i,0,0);
sort(q+1,q+k*2+1),solve(1,k*2,1,tot,1,1000000000);
for(int i=1;i<=k;i++)printf("%d\n",ans[i]);
}
Day1T2菜肴制作:
一开始想的是用优先队列来做,但是不对
我们要求小的尽量在前面,而不是前面的尽量小,优先队列符合后者
考虑转化一下,前面的尽量小就是大的尽量在后面,同理,小的尽量在前面就是后面的尽量大
所以建个反向图跑拓扑序就完了
Code:
#include<bits/stdc++.h>
using namespace std;
inline int read(){
int res=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
return res*f;
}
const int N=1e5+5;
int in[N],vis[N<<1],head[N],nxt[N<<1],tot=0;
inline void add(int x,int y){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;in[y]++;}
priority_queue<int>q;
int ans[N],cnt=0,n;
inline bool topsort(){
for(int i=1;i<=n;i++) if(!in[i]) q.push(i);
while(!q.empty()){
int x=q.top();q.pop();ans[++cnt]=x;
for(int i=head[x];i;i=nxt[i]){
int y=vis[i];--in[y];
if(!in[y]) q.push(y);
}
}
for(int i=1;i<=n;i++) if(in[i]) return false;
return true;
}
int main(){
int t=read();
while(t--){
memset(in,0,sizeof(in));
memset(head,0,sizeof(head));
cnt=tot=0;
n=read();int m=read();
for(int x,y,i=1;i<=m;i++) x=read(),y=read(),add(y,x);
if(!topsort()) {puts("Impossible!");continue;}
for(int i=n;i;i--) cout<<ans[i]<<" ";
cout<<"\n";
}
return 0;
}
Day1T3落忆枫音:如果没有额外的边,那么考虑乘法原理得到答案就是
∏
i
=
2
n
i
n
[
i
]
\prod_{i=2}^n{in[i]}
∏i=2nin[i]
加上额外的边后,可能生成环(如果没有就一样的)
多出来的选择就是环上的点都选了环上的父亲
那么我们设
f
[
x
]
f[x]
f[x]是
x
x
x到
s
s
s(给出的额外边)上产生的贡献,转移就是
f
[
x
]
=
∑
f
[
t
o
[
x
]
]
/
i
n
[
x
]
f[x]=\sum{f[to[x]]/in[x]}
f[x]=∑f[to[x]]/in[x]
Code:
#include<bits/stdc++.h>
#define ll long long
#define mod 1000000007
using namespace std;
inline int read(){
int res=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
return res*f;
}
const int N=1e5+5;
int vis[N<<1],head[N<<1],nxt[N<<1],tot=0;
int in[N],sum=1;
inline void add(int x,int y){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
inline int ksm(int a,int b){int res=1; for(;b;b>>=1){if(b&1) res=(ll)res*a%mod;a=(ll)a*a%mod;} return res;}
int pt[N],s,t,f[N];
void dfs(int v){
if(pt[v]) return;
pt[v]=1;
if(v==t) {f[v]=(ll)sum*ksm(in[v],mod-2)%mod;return;}
for(int i=head[v];i;i=nxt[i])
dfs(vis[i]),f[v]=(f[v]+f[vis[i]])%mod;
f[v]=(ll)f[v]*ksm(in[v],mod-2)%mod;
}
int ans=1;
int main(){
int n=read(),m=read();s=read(),t=read();
for(int x,y,i=1;i<=m;i++) x=read(),y=read(),add(y,x),++in[y];
in[1]++;
for(int i=1;i<=n;++i) {
if(i==t) ans=(ll)(in[i]+1)*ans%mod;
else ans=(ll)in[i]*ans%mod;
sum=(ll)in[i]*sum%mod;
}
dfs(s);
ans=(ans+mod-f[s])%mod;
cout<<ans<<endl;
return 0;
}
Day2T1开店:可以点分树做,也可以树链剖分+主席树
询问
∑
v
∈
[
l
,
r
]
d
i
s
(
u
,
v
)
\sum_{v∈[l,r]}{dis(u,v)}
∑v∈[l,r]dis(u,v)就是求
(
r
−
l
+
1
)
∗
d
e
p
[
u
]
+
∑
v
∈
[
l
,
r
]
d
e
p
[
v
]
−
2
∗
∑
v
∈
[
l
,
r
]
d
e
p
[
l
c
a
(
u
,
v
)
]
(r-l+1)*dep[u]+\sum_{v∈[l,r]}dep[v]-2*\sum_{v∈[l,r]}{dep[lca(u,v)]}
(r−l+1)∗dep[u]+∑v∈[l,r]dep[v]−2∗∑v∈[l,r]dep[lca(u,v)],前面两项
O
(
1
)
O(1)
O(1),最后一项实际上是把每个点到根的路径上的所有点打上一个标记,然后询问u到根节点的路径上的标记数,如果没有年龄的限制,就直接线段树做了
有限制的话,按照年龄排序建主席树,然后区间查询就很简单了
Code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int res=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
return res*f;
}
const int N=1.5e5+5;
ll presum[N],sum[N];
namespace President_tree{
struct seg{int ls,rs,cnt;ll sum;}tr[N*150];int tot;
#define lc(k) tr[k].ls
#define rc(k) tr[k].rs
void build(int &k,int l,int r){
k=++tot;
if(l==r) return;
int mid=l+r>>1;
build(lc(k),l,mid);build(rc(k),mid+1,r);
}
void ins(int &rt1,int l,int r,int ql,int qr){
tr[++tot]=tr[rt1];
if(l==ql && r==qr){++tr[rt1=tot].cnt;return;}
tr[rt1=tot].sum+=sum[qr]-sum[ql-1];
int mid=l+r>>1;
if(qr<=mid) ins(lc(rt1),l,mid,ql,qr);
else if(ql>mid) ins(rc(rt1),mid+1,r,ql,qr);
else ins(lc(rt1),l,mid,ql,mid),ins(rc(rt1),mid+1,r,mid+1,qr);
}
ll query(int rt,int l,int r,int ql,int qr){
ll res=1ll*(sum[qr]-sum[ql-1])*tr[rt].cnt;
if(l==ql && r==qr) return res+tr[rt].sum;
int mid=l+r>>1;
if(qr<=mid) return res+query(lc(rt),l,mid,ql,qr);
else if(ql>mid) return res+query(rc(rt),mid+1,r,ql,qr);
else return res+query(lc(rt),l,mid,ql,mid)+query(rc(rt),mid+1,r,mid+1,qr);
}
}
using namespace President_tree;
int n,rt[N];
namespace tree{
int vis[N<<1],nxt[N<<1],head[N],c[N<<1],tot=0;
inline void add(int x,int y,int z){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;c[tot]=z;}
int siz[N],fa[N],hson[N],dep[N],pt[N];
void dfs1(int v){
pt[v]=siz[v]=1;
for(int i=head[v];i;i=nxt[i]){
int y=vis[i];
if(pt[y]) continue;
fa[y]=v;dep[y]=dep[v]+c[i];
dfs1(y);
siz[v]+=siz[y];
if(siz[y]>siz[hson[v]]) hson[v]=y;
}
}
int dfn[N],id[N],sign=0,top[N];
void dfs2(int v){
dfn[v]=++sign,sum[sign]=dep[v]-dep[fa[v]];
if(hson[v]) top[hson[v]]=top[v],dfs2(hson[v]);
for(int i=head[v];i;i=nxt[i])
if(!top[vis[i]]) top[vis[i]]=vis[i],dfs2(vis[i]);
}
inline ll ask(int rt1,int v){
ll res=0;
while(top[v]!=1) res+=query(rt[rt1],1,n,dfn[top[v]],dfn[v]),v=fa[top[v]];
return res+query(rt[rt1],1,n,1,dfn[v]);
}
}
using namespace tree;
struct point{
int age,id;
point(){}
point(int _age,int _id):age(_age),id(_id){}
}p[N];
inline bool operator < (point a,point b){return a.age==b.age?a.id<b.id:a.age<b.age;}
ll ans=0;
int main(){
n=read();int q=read(),mod=read();
for(int x,i=1;i<=n;i++) x=read(),p[i]=point(x,i);
sort(p+1,p+n+1);
for(int x,y,z,i=1;i<n;i++){
x=read();y=read();z=read();
add(x,y,z);add(y,x,z);
}
dfs1(1);top[1]=1;dfs2(1);
for(int i=1;i<=n;i++) sum[i]+=sum[i-1],presum[i]=presum[i-1]+dep[p[i].id];
build(rt[0],1,n);
for(int i=1;i<=n;i++){
int v=p[i].id;rt[i]=rt[i-1];
while(top[v]!=1) ins(rt[i],1,n,dfn[top[v]],dfn[v]),v=fa[top[v]];
ins(rt[i],1,n,1,dfn[v]);
}
while(q--){
int u=read(),l=read(),r=read();
l=(1ll*l+ans)%mod,r=(1ll*r+ans)%mod;
if(l>r) swap(l,r);
l=lower_bound(p+1,p+n+1,point(l,0))-p;
r=upper_bound(p+1,p+n+1,point(r,n))-p-1;
ans=1ll*(r-l+1)*dep[u]+presum[r]-presum[l-1]-2*(ask(r,u)-ask(l-1,u));
cout<<ans<<"\n";
}
return 0;
}
Day2T2试验比较:实际上是给出一个森林,等号的条件用并查集缩起来,把每个森林的根连向0号节点就是一棵树,要给出一个序列使得父亲严格在儿子前面,考虑树形DP,
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示子树
i
i
i中的排列有恰好
j
j
j个小于号的方案数,每次加入一个v的一个子树y形成一个恰好
k
k
k个小于号的排列的转移就是
t
m
p
[
k
]
=
∑
i
=
1
s
i
z
[
v
]
∑
j
=
1
s
i
z
[
y
]
f
[
v
]
[
i
]
∗
f
[
y
]
[
j
]
∗
C
k
i
C
i
j
−
(
k
−
i
)
tmp[k]=\sum_{i=1}^{siz[v]}\sum_{j=1}^{siz[y]}f[v][i]*f[y][j]*C_{k}^iC_{i}^{j-(k-i)}
tmp[k]=∑i=1siz[v]∑j=1siz[y]f[v][i]∗f[y][j]∗CkiCij−(k−i)
其中tmp是一个中间数组,
s
i
z
[
v
]
siz[v]
siz[v]为
v
v
v的已经转移过的儿子的
s
i
z
siz
siz和,且
k
k
k满足
m
a
x
(
i
,
j
)
≤
k
≤
i
+
j
max(i,j)\le k \le i+j
max(i,j)≤k≤i+j
转移的意义是将i个白球,j个黑球放入k个盒子中,同一个盒子中不能有同色球,且每个盒子不为空的方案数,即先选
i
i
i个盒子放白球,剩下的
k
−
i
k-i
k−i个盒子放黑球,现在剩下
j
−
(
k
−
i
)
j-(k-i)
j−(k−i)个黑球,放到
i
i
i个放过白球的盒子中,然后就这样转移就完了
Code:
#include<bits/stdc++.h>
#define mod 1000000007
using namespace std;
inline int read(){
int res=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
return res*f;
}
const int N=105;
inline int Add(int x,int y){x+=y;if(x>=mod) x-=mod;return x;}
inline void inc(int &x,int y){x+=y;if(x>=mod) x-=mod;}
inline int mul(int x,int y){return 1ll*x*y%mod;}
inline void Mul(int &x,int y){x=1ll*x*y%mod;}
int vis[N],nxt[N],head[N],tot=0;
inline void add(int x,int y){vis[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
int f[N][N];
int siz[N],C[N][N],tmp[N];
void dp(int v){
int flag=1;
for(int e=head[v];e;e=nxt[e]){
int y=vis[e];dp(y);
if(flag){
flag=0;siz[v]+=siz[y];
for(int i=1;i<=siz[v];i++) f[v][i]=f[y][i];
}
else{
memset(tmp,0,sizeof(tmp));
for(int i=1;i<=siz[v];i++) if(f[v][i])
for(int j=1;j<=siz[y];j++) if(f[y][j])
for(int k=max(i,j);k<=i+j;k++)
inc(tmp[k],mul(mul(f[v][i],f[y][j]),mul(C[k][i],C[i][j-(k-i)])));
siz[v]+=siz[y];
for(int i=1;i<=siz[v];i++) f[v][i]=tmp[i];
}
}
if(flag) f[v][0]=1;++siz[v];
for(int i=siz[v];i;i--) f[v][i]=f[v][i-1];
}
int fa[N],Fa[N],isrt[N];
int get(int x){return fa[x]?fa[x]=get(fa[x]):x;}
int pt[N];
bool check(int v){
if(pt[v]) return false;
pt[v]=1;
for(int i=head[v];i;i=nxt[i]) if(!check(vis[i])) return false;
return true;
}
char ch[5];
int main(){
int n=read(),m=read();
for(int i=0;i<=n;i++){
C[i][0]=1;
for(int j=1;j<=i;j++) C[i][j]=Add(C[i-1][j],C[i-1][j-1]);
}
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%s%d",&x,ch,&y);
if(ch[0]=='='){
int xx=get(x),yy=get(y);
if(xx!=yy) fa[xx]=yy,isrt[xx]=1,Fa[yy]=max(Fa[yy],Fa[xx]);
}
else Fa[y]=x;
}
for(int i=1;i<=n;i++) if(!isrt[i]) add(get(Fa[i]),i);
for(int i=0;i<=n;i++) if(!pt[i]) if(!check(i)) {puts("0");return 0;}
dp(0);
int ans=0;
for(int i=1;i<=siz[0];i++) inc(ans,f[0][i]);
cout<<ans;
return 0;
}
Day2T3亚瑟王:
考虑算概率最后乘上d,因为直接计算每张卡每轮的概率不现实(无法转移),所以考虑一个整体并且可以表示出每张卡的状态,
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示
m
m
m轮中前
i
i
i张卡用了
j
j
j张的概率,则要表示一张卡
i
i
i在
m
m
m轮中出现过的概率就是
f
[
i
−
1
]
[
j
]
f[i-1][j]
f[i−1][j]乘上必须选
i
i
i的概率,不选
i
i
i的概率就是在后面
m
−
j
m-j
m−j轮中都跳过了
i
i
i的概率,就是
(
1
−
p
[
i
]
)
m
−
j
(1-p[i])^{m-j}
(1−p[i])m−j,用1减去不选的概率就是选的概率
然后转移就是考虑选不选
i
i
i,选不选
i
i
i的概率就如上面所述,方程为
f
[
i
]
[
j
]
=
f
[
i
−
1
]
[
j
]
∗
(
1
−
p
[
i
]
)
m
−
j
+
f
[
i
−
1
]
[
j
−
1
]
∗
(
1
−
(
1
−
p
[
i
]
)
m
−
j
+
1
)
)
f[i][j]=f[i-1][j]*(1-p[i])^{m-j}+f[i-1][j-1]*(1-(1-p[i])^{m-j+1}))
f[i][j]=f[i−1][j]∗(1−p[i])m−j+f[i−1][j−1]∗(1−(1−p[i])m−j+1))
Code:
#include<bits/stdc++.h>
#define db double
using namespace std;
inline int read(){
int res=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
return res*f;
}
const int N=225;
inline db ksm(db a,int b){db res=1.0;for(;b;b>>=1,a*=a) if(b&1) res*=a;return res;}
db f[N][N],p[N];
int d[N];
int n,m;
int main(){
int t=read();
while(t--){
memset(f,0,sizeof(f));
n=read();m=read();
for(int i=1;i<=n;++i){
scanf("%lf",&p[i]);
d[i]=read();
}
f[0][0]=1;
for(int i=1;i<=n;++i)
for(int j=0;j<=min(i,m);++j){
if(!j) f[i][j]=f[i-1][j]*ksm(1-p[i],m-j);
else f[i][j]=f[i-1][j]*ksm(1-p[i],m-j)+f[i-1][j-1]*(1-ksm(1-p[i],m-j+1));
}
db res=0;
for(int i=1;i<=n;++i){
db now=0;
for(int j=0;j<=min(i-1,m);++j) if(j!=m) now+=f[i-1][j]*(1-ksm(1-p[i],m-j));
res+=now*d[i];
}
printf("%.10lf\n",res);
}
return 0;
}