【前言】
这是打的第二场,rk39,但是AB这两个比较简单的题都没做emm,大概还是磨合的不够。然后感觉对于阈值类的东西还不是很敏感,应该看到不太好做就直接去想这种阈值的。校内3/9(然后就开启了常年校内第三的生活,前面一个银川冠军,一个大三队)
A. Arithmetic Progression
【题意】
给定一个序列 a i a_i ai,求满足排序后是等差数列的区间个数。
n ≤ 1 0 5 n\leq 10^5 n≤105
【思路】
首先需要一个快速的判定方法:
对于序列 b i b_i bi,若其排序后为等差数列,则必然有公差 d = g c d ( b 2 − b 1 , b 3 − b 2 , . . . ) d=gcd(b_2-b_1,b_3-b_2,...) d=gcd(b2−b1,b3−b2,...)
于是问题转化为统计区间
(
l
,
r
)
(l,r)
(l,r)满足:
max
(
a
[
l
.
.
r
]
)
−
min
(
a
[
l
.
.
r
]
)
=
(
r
−
l
)
⋅
∣
g
c
d
(
.
.
.
)
∣
\max(a[l..r])-\min(a[l..r])=(r-l) \cdot |gcd(...)|
max(a[l..r])−min(a[l..r])=(r−l)⋅∣gcd(...)∣
枚举
r
r
r,可以在结合单调栈在线段树上维护
max
−
min
\max-\min
max−min的值,处理区间修改操作,对于不同的
l
,
g
c
d
l,gcd
l,gcd的不同分段只有
log
a
\log a
loga段,可以在
l
l
l每次
g
c
d
gcd
gcd改变时在线段树上做单点修改
又 max ( l , r ) − min ( l , r ) − ( r − l ) ⋅ ∣ g c d ( . . . ) ∣ ≥ 0 \max(l,r)-\min(l,r)-(r-l) \cdot |gcd(...)|\geq 0 max(l,r)−min(l,r)−(r−l)⋅∣gcd(...)∣≥0,故可以统计线段树上的最小值及其出现的次数,就能得到区间内该表达式为 0 0 0的个数
复杂度 O ( n log 2 n ) O(n\log^2 n) O(nlog2n)
【参考代码】
#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=3e5+5;
int n,m;
ll mn[maxn<<2],cnt[maxn<<2],tag[maxn<<2];
void add(int v,int ad){
mn[v]+=ad;
tag[v]+=ad;
}
#define ls (v<<1)
#define rs (v<<1|1)
#define mid ((l+r)>>1)
void push_down(int v){
if(tag[v]){
add(ls,tag[v]);add(rs,tag[v]);
tag[v]=0;
}
}
void push_up(int v){
mn[v]=min(mn[ls],mn[rs]);
cnt[v]=mn[ls]==mn[rs]?cnt[ls]+cnt[rs]:
(mn[ls]<mn[rs]?cnt[ls]:cnt[rs]);
}
void modify(int v,int l,int r,int al,int ar,ll ad){
if(al<=l&&ar>=r) return add(v,ad);
push_down(v);
if(al<=mid) modify(ls,l,mid,al,ar,ad);
if(ar>mid) modify(rs,mid+1,r,al,ar,ad);
push_up(v);
}
void build(int v,int l,int r){
mn[v]=tag[v]=0;cnt[v]=1;if(l==r) {return; }
build(ls,l,mid);build(rs,mid+1,r);
push_up(v);
}
ll query(int v,int l,int r,int al,int ar,ll z){
if(al<=l&&ar>=r) return mn[v]==z?cnt[v]:0;
push_down(v);
ll ans=0;
if(al<=mid) ans+=query(ls,l,mid,al,ar,z);
if(ar>mid) ans+=query(rs,mid+1,r,al,ar,z);
return ans;
}
int a[maxn];
int b[maxn];
int s1[maxn],s2[maxn];
int c[maxn],w[maxn];
int t1,t2,tp;
signed main(){
dwn(_,yh(),1){
n=yh();
rep(i,1,n){
a[i]=yh();
b[i]=abs(a[i]-a[i-1]);
}
ll ans=0;
build(1,1,n);
t1=0;t2=0;tp=0;
rep(i,1,n){
for(;t1&&a[s1[t1]]<a[i];t1--)modify(1,1,n,s1[t1-1]+1,s1[t1],a[i]-a[s1[t1]]);
for(;t2&&a[s2[t2]]>a[i];t2--)modify(1,1,n,s2[t2-1]+1,s2[t2],a[s2[t2]]-a[i]);
s1[++t1]=i;s2[++t2]=i;
if(i==1)continue;
w[++tp]=b[i];
c[tp]=i-1;
modify(1,1,n,i-1,i-1,1ll*(i-1)*b[i]);
dwn(i,tp-1,1){
int nw=__gcd(w[i+1],w[i]);if(nw==w[i])continue;
rep(j,c[i-1]+1,c[i])
modify(1,1,n,j,j,1ll*j*(nw-w[i]));
w[i]=nw;
}
int nt=0;rep(i,1,tp) if(w[i]!=w[i+1]||i==tp){
c[++nt]=c[i];
w[nt]=w[i];
}
tp=nt;
rep(j,1,tp) ans+=query(1,1,n,c[j-1]+1,c[j],1ll*i*w[j]);
}
cout<<ans+n<<hvie;
}
return 0;
}
B. Cannon
【题意】
有一个 2 × 1 0 100 2\times 10^{100} 2×10100的棋盘,其中第一行摆了 a a a个炮,第二行摆了 b b b个炮,求依次发生 k k k个炮吃炮事件的方案数。
并且求考虑两行之间顺序以及不考虑两行之间顺序的两个问题的答案。
a , b ≤ 5 × 1 0 6 a,b\leq 5\times 10^6 a,b≤5×106
【思路】
容易发现包含 n n n 个炮的一行吃一次的方案数为 2 ( n − 2 ) 2(n-2) 2(n−2),则 n n n个炮操作 $m $次的方案数为 2 m ( n − 2 ) ! ( n − 2 − m ) ! 2^m \frac{(n-2)!}{(n-2-m)!} 2m(n−2−m)!(n−2)!
设 n = a − 2 , m = b − 2 n=a-2,m=b-2 n=a−2,m=b−2 ,则两个问题分别是对于每个 k k k求
- 2 k ∑ k ! i ! ( k − i ) ! n ! ( n − i ) ! m ! ( m − ( k − i ) ) ! 2^k\sum \frac {k!}{i!(k-i)!}\frac {n!}{(n-i)!}\frac {m!}{(m-(k-i))!} 2k∑i!(k−i)!k!(n−i)!n!(m−(k−i))!m!
- 2 k ∑ n ! ( n − i ) ! m ! ( m − ( k − i ) ) ! 2^k\sum\frac {n!}{(n-i)!}\frac {m!}{(m-(k-i))!} 2k∑(n−i)!n!(m−(k−i))!m!
对前一个问题,容易发现:
2
k
∑
k
!
i
!
(
k
−
i
)
!
n
!
(
n
−
i
)
!
m
!
(
m
−
(
k
−
i
)
)
!
=
2
k
∑
k
!
(
n
i
)
(
m
k
−
i
)
=
2
k
k
!
(
n
+
m
k
)
\begin{aligned} &2^k\sum \frac {k!}{i!(k-i)!}\frac {n!}{(n-i)!}\frac {m!}{(m-(k-i))!}\\=&2^k\sum k!\binom{n}{i}\binom{m}{k-i}\\ =&2^kk!\binom{n+m}{k} \end{aligned}
==2k∑i!(k−i)!k!(n−i)!n!(m−(k−i))!m!2k∑k!(in)(k−im)2kk!(kn+m)
对于第二个问题,那么有:
2
k
∑
n
!
(
n
−
i
)
!
m
!
(
m
−
(
k
−
i
)
)
!
=
2
k
∑
n
!
m
!
(
n
+
m
−
k
)
!
(
n
+
m
−
k
)
!
(
n
−
i
)
!
(
m
−
(
k
−
i
)
)
!
=
2
k
n
!
m
!
(
n
+
m
−
k
)
!
∑
n
−
k
n
(
n
+
m
−
k
i
)
\begin{aligned} &2^k\sum \frac {n!}{(n-i)!}\frac {m!}{(m-(k-i))!}\\=&2^k\sum \frac{n!m!}{(n+m-k)!}\frac {(n+m-k)!}{(n-i)!(m-(k-i))!}\\ =&2^k\frac {n!m!}{(n+m-k)!}\sum_{n-k}^n\binom{n+m-k}{i} \end{aligned}
==2k∑(n−i)!n!(m−(k−i))!m!2k∑(n+m−k)!n!m!(n−i)!(m−(k−i))!(n+m−k)!2k(n+m−k)!n!m!n−k∑n(in+m−k)
这是一个组合数关于
m
m
m的前缀和问题,枚举
k
k
k以后,可以用步移方法解决:
S
(
n
,
m
)
=
∑
i
=
0
m
C
(
n
,
i
)
S
(
n
,
m
+
1
)
=
S
(
n
,
m
)
+
C
(
n
,
m
+
1
)
S
(
n
,
m
−
1
)
=
S
(
n
,
m
)
−
C
(
n
,
m
)
S
(
n
+
1
,
m
)
=
∑
i
=
0
m
C
(
n
+
1
,
i
)
=
∑
i
=
0
m
C
(
n
,
i
)
+
C
(
n
,
i
−
1
)
=
2
S
(
n
,
m
)
−
C
(
n
,
m
)
\begin{aligned} &S(n,m)=\sum_{i=0}^mC(n,i)\\ &S(n,m+1)=S(n,m)+C(n,m+1)\\ &S(n,m-1)=S(n,m)-C(n,m) \\ &S(n+1,m)=\sum_{i=0}^mC(n+1,i)=\sum_{i=0}^mC(n,i)+C(n,i-1)=2S(n,m)-C(n,m) \end{aligned}
S(n,m)=i=0∑mC(n,i)S(n,m+1)=S(n,m)+C(n,m+1)S(n,m−1)=S(n,m)−C(n,m)S(n+1,m)=i=0∑mC(n+1,i)=i=0∑mC(n,i)+C(n,i−1)=2S(n,m)−C(n,m)
那么复杂度就是 O ( a + b ) O(a+b) O(a+b)了。
#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=2e7+5,mod=1e9+9;
ll fac[maxn],ifac[maxn],pw[maxn];
ll ksm(ll x,ll p){
ll ans=1;
for(;p;p>>=1,x=x*x%mod) ans=(p&1)?ans*x%mod:ans;
return ans;
}
ll C(ll n,ll m){
if(n<m) return 0;
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
signed main(){
int n=yh()-2,m=yh()-2;
fac[0]=1;pw[0]=1;
rep(i,1,n+m) fac[i]=fac[i-1]*i%mod;
ifac[n+m]=ksm(fac[n+m],mod-2);
dwn(i,n+m-1,0) ifac[i]=ifac[i+1]*(i+1)%mod;
ll ans1=0,ans2=0,S1=0,S2=0;
rep(i,1,n+m) pw[i]=pw[i-1]*2%mod;
rep(i,0,n+m){
ll tmp=fac[i]*pw[i]%mod*C(n+m,i)%mod;
ans1^=tmp;
}
cout<<ans1<<" ";
rep(i,0,n) S1=(S1+C(n+m,i))%mod;
ll i2=ksm(2ll,mod-2);
S2=(S1-C(n+m,n)+mod)%mod;
for(int i=0;i<=n+m;i++,
S1=(S1+C(n+m-i,n))%mod*i2%mod,
S2=(S2+C(n+m-i,n-i))%mod*i2%mod,
S2=(S2-C(n+m-i,n-i)+mod)%mod){
// cout<<S1<<" "<<S2<<hvie;
ll tmp=fac[n]*fac[m]%mod*pw[i]%mod*ifac[n+m-i]%mod*(S1-S2+mod)%mod;
ans2^=tmp;
}
cout<<ans2<<hvie;
return 0;
}
C. Draw Grids
【题意】
给定一个 n × m n\times m n×m的点阵,每次选择相邻两个点连线,两个人轮流操作,不能画出封闭图形,不能操作的人输。
【思路】
不能封闭即图始终是一片森林,那么终态一定是一颗生成树,因此根据点数的奇偶性即可判断结果。
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=3e5+5;
int main(){
int n,m;
cin>>n>>m;
if((n*m-1)&1){
puts("YES");
}else puts("NO");
return 0;
}
D. Er Ba Game
【题目】
略
【思路】
按照题意模拟即可
【参考代码】
/*
* @date:2021-07-19 12:01:01
* @source:
*/
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {return y > x ? x = y, 1 : 0;}
template<class T, class G> bool chkMin(T &x, G y) {return y < x ? x = y, 1 : 0;}
int T;
int a1, a2, b1, b2;
bool equal(int a, int b) {return a == b;}
bool big(int a, int b) {return a == 2 && b == 8; }
int cmp(int a1, int b1, int a2, int b2) {
if (a1 > b1) swap(a1, b1);
if (a2 > b2) swap(a2, b2);
if (a1 == a2 && b1 == b2) return 2;
if (big(a1, b1) && !big(a2, b2)) return 0;
if (big(a2, b2) && !big(a1, b1)) return 1;
if (equal(a1, b1) && equal(a2, b2)) {
return a1 < a2;
}
if (equal(a1, b1) && !equal(a2, b2)) return 0;
if (equal(a2, b2) && !equal(a1, b1)) return 1;
if ((a1 + b1) % 10 != (a2 + b2) % 10) {
return (a1 + b1) % 10 < (a2 + b2) % 10;
} else {
return b1 < b2;
}
}
int main() {
scanf("%d", &T);
while (T--) {
scanf("%d%d%d%d", &a1, &b1, &a2, &b2);
int ans = cmp(a1, b1, a2, b2);
if (ans == 0) puts("first");
else if (ans == 1) puts("second");
else puts("tie");
}
return 0;
}
E. Gas Station
【题意】
给定一棵带边权和点权的树,有 Q Q Q次询问,每次从 s s s出发,初始权值是 x x x,不允许经过某个点 p p p。经过一条边消耗为边权,每到一个增加点权,要求过程中权值非负,求可以到达的点的个数。
n , Q ≤ 1 0 5 n,Q\leq 10^5 n,Q≤105
【思路】
点分治,每次求跨过根的可达点的个数。
可以预处理每个点能不能到该根,从这个根下去需要多少初始权值。
讨论 p p p所在位置,减去跨过 p p p或自己子树内的部分,求子树内答案可以先对预处理的权值离散化,最后BIT+DFS维护。
复杂度 O ( n log 2 n ) O(n\log^2 n) O(nlog2n),实现见代码。
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define int long long
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=4e5+10,mod=1e9+7,inf=0x3f3f3f3f;
int a[N],ans[N];
pii b[N];
vector<pii>G[N];
vector<int>vec[N];
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return f?ret:-ret;
}
struct node
{
int d,id,k;
node(int d=0,int id=0,int k=0):d(d),id(id),k(k){}
};
vector<node>q[N];
int siz[N],son[N],vis[N],rt,sum;
void getroot(int x,int f)
{
siz[x]=1;son[x]=0;
for(auto t:G[x])
{
int v=t.fi;
if(vis[v] || v==f) continue;
getroot(v,x);siz[x]+=siz[v];son[x]=max(son[x],siz[v]);
}
son[x]=max(son[x],sum-siz[x]);
if(son[x]<son[rt]) rt=x;
}
struct BIT
{
#define lowbit(x) (x&(-x))
int c[N],lim;
void init(int x){lim=x;memset(c,0,(x+2)*4);}
void add(int x)
{
for(;x<=lim;x+=lowbit(x)) c[x]++;
}
int query(int x)
{
int ret=0;
for(;x;x-=lowbit(x)) ret+=c[x];
return ret;
}
}bit;
int cnt,dfn;
int H[N],st[N],ed[N],bl[N],A[N],B[N],D[N],fr[N];
void dfs1(int x,int f,int ds)
{
H[++cnt]=-A[x];st[x]=++dfn;fr[dfn]=x;
for(auto t:G[x])
{
int v=t.fi,w=t.se;
if(vis[v] || v==f) continue;
A[v]=min(A[x],ds-w);
B[v]=min(B[x]+a[v]-w,0ll);
D[v]=D[x]-w+a[v];
bl[v]=f?bl[x]:v;
dfs1(v,x,ds-w+a[v]);
}
ed[x]=dfn;
}
void dfs2(int x,int f)
{
for(auto &i:q[x]) ans[i.id]-=i.k*bit.query(i.d=upper_bound(H+1,H+cnt+1,i.d)-H-1);
bit.add(lower_bound(H+1,H+cnt+1,-A[x])-H);
for(auto v:G[x])
if(!vis[v.fi] && v.fi!=f) dfs2(v.fi,x);
for(auto &i:q[x]) ans[i.id]+=i.k*bit.query(i.d);
ed[x]=0;q[x].clear();
}
void solve(int x)
{
vis[x]=1;
dfn=A[x]=B[x]=D[x]=bl[x]=cnt=0;
dfs1(x,0,a[x]);
sort(H+1,H+cnt+1);cnt=unique(H+1,H+cnt+1)-H-1;
for(int j=1;j<=dfn;++j)
{
int u=fr[j];
for(auto i:vec[u])
{
if(B[u]+b[i].fi<0) continue;//can't to rt
if(st[b[i].se]<=st[u] && ed[u]<=ed[b[i].se]) continue;//blocked
int ds=D[u]+b[i].fi;
q[x].pb(node(ds,i,1));
if(bl[u]) q[bl[u]].pb(node(ds,i,-1));
if(ed[b[i].se] && (st[b[i].se]<st[bl[u]] || st[b[i].se]>ed[bl[u]]))
q[b[i].se].pb(node(ds,i,-1));
}
}
bit.init(cnt);dfs2(x,0);
for(auto v:G[x])
{
if(vis[v.fi]) continue;
rt=0;sum=siz[v.fi];getroot(v.fi,x);solve(rt);
}
}
signed main()
{
int n=read(),Q=read();
for(int i=2;i<=n;++i)
{
int u=read(),v=read(),w=read();
G[u].pb(mkp(v,w));G[v].pb(mkp(u,w));
}
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=Q;++i)
{
int x=read(),td=read(),tp=read();
vec[x].pb(i);
b[i]=mkp(td,tp);
}
son[0]=sum=n;getroot(1,0);solve(rt);
for(int i=1;i<=Q;++i) printf("%lld\n",ans[i]);
return 0;
}
F. Girlfriend
【题意】
空间中有六个点,满足 ∣ P 1 A ∣ ≥ k 1 ∣ P 1 B ∣ , ∣ P 2 C ∣ ≥ k 2 ∣ P 2 D ∣ |P_1A|\geq k_1|P_1B|,|P_2C|\geq k_2|P_2D| ∣P1A∣≥k1∣P1B∣,∣P2C∣≥k2∣P2D∣
求 P 1 , P 2 P_1,P_2 P1,P2各自轨迹围成的空间体的体积交
【思路】
对于固定的 k k k,事实上 P P P的轨迹围成一个阿波罗尼斯球,这个通过解上面不等式的方程可以的出来结论。
那么问题就可以转化为求球的体积交。
【参考代码】
#include<bits/stdc++.h>
#define double long double
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
struct vec{
double x,y,z;
vec(){}vec(double x,double y,double z):x(x),y(y),z(z){}
};
void trans(double A,double B,double C,double D,vec&P,double &R2){
P=vec(-A/2.,-B/2.,-C/2.);
R2=(A*A+B*B+C*C)/4-D;
// cout<<P.x<<" "<<P.y<<" "<<P.z<<" "<<R2<<hvie;
}
double k1,k2;
void toabcd(double k,double x1,double y1,double z1,double x2,double y2,double z2,double &A,double &B,double &C,double &D){
A=-2*k*x2+2*x1;
B=-2*k*y2+2*y1;
C=-2*k*z2+2*z1;
D=k*(x2*x2+y2*y2+z2*z2)-(x1*x1+y1*y1+z1*z1);
A/=k-1; B/=k-1; C/=k-1; D/=k-1;
}
//2球体积交
double pi;
double pow2(double x){
return x*x;
}
double pow3(double x){return x*x*x;}
double dis(double x,double y,double z,double a,double b,double c){
return (pow2(x-a)+pow2(y-b)+pow2(z-c));
}
double cos(double a,double b,double c){return (b*b+c*c-a*a)/(2*b*c);}
double cap(double r,double h){return pi*(r*3-h)*h*h/3;}
double sphere_intersect(double x1,double y1,double z1,double r1,double x2,double y2,double z2,double r2)
{
double d=dis(x1,y1,z1,x2,y2,z2);
//相离
if(d>=pow2(r1+r2))return 0;
//包含
if(d<=pow2(r1-r2))return pow3(min(r1,r2))*4*pi/3;
//相交
double h1=r1-r1*cos(r2,r1,sqrt(d)),h2=r2-r2*cos(r1,r2,sqrt(d));
return cap(r1,h1)+cap(r2,h2);
}
int main(){
pi=acos(-1);
dwn(_,yh(),1){
vec A,B,C,D;
A.x=yh();A.y=yh();A.z=yh();
B.x=yh();B.y=yh();B.z=yh();
C.x=yh();C.y=yh();C.z=yh();
D.x=yh();D.y=yh();D.z=yh();
k1=yh();k2=yh();
k1=k1*k1;k2=k2*k2;
double a,b,c,d;
vec C1,C2;
double r1,r2;
toabcd(k1,A.x,A.y,A.z,B.x,B.y,B.z,a,b,c,d);
trans(a,b,c,d,C1,r1);r1=sqrt(r1);
toabcd(k2,C.x,C.y,C.z,D.x,D.y,D.z,a,b,c,d);
trans(a,b,c,d,C2,r2);r2=sqrt(r2);
cout<<fixed<<setprecision(10)<<sphere_intersect(C1.x,C1.y,C1.z,r1,C2.x,C2.y,C2.z,r2)<<hvie;
}
return 0;
}
G. League of Legends
【题意】
给定 n n n个区间,要求将它们分成 k k k组,每组要有交,最大化每组交长度之和。
n , k ≤ 5000 n,k\leq 5000 n,k≤5000
【思路】
首先一个显然的简化是,对于每一个包含其他区间的大区间,它的最优决策有两种:
- 归属到一个被它包含区间所在的组,这样不影响答案
- 独自为一组,长度直接计入答案
那么剩下的部分就是左右端点均单调递增的小区间了,排序后,记 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个,分了 j j j组的最优值,那么转移就是 f [ i ] [ j ] + r [ i ] − l [ j ] → f [ k ] [ j + 1 ] f[i][j]+r[i]-l[j]\rightarrow f[k][j+1] f[i][j]+r[i]−l[j]→f[k][j+1],且要满足 r [ i ] > l [ j ] r[i]>l[j] r[i]>l[j],这个显然可以用单调队列优化。
最后求出了 f [ n ′ ] [ i ] f[n'][i] f[n′][i]以后,综合大区间的贡献,枚举这个 i i i讨论区多少个大区间即可。
复杂度 O ( n 2 ) O(n^2) O(n2)
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=3e5+5,inf=0x3f3f3f3f;
pii s[maxn];
int n,m;
int q[maxn];
ll b[maxn];
ll f[5005][5005];
int main(){
n=yh(),m=yh();
rep(i,1,n) s[i].fi=yh(),s[i].se=yh();
sort(s+1,s+1+n);
int mn=inf,cnt=0;
dwn(i,n,1){
if(s[i].se>=mn) b[++cnt]=s[i].se-s[i].fi, s[i].fi=inf;
else mn=s[i].se;
}
memset(f,~0x3f,sizeof(f));
f[0][0]=0;
sort(s+1,s+1+n);
n-=cnt;
// rep(i,1,n)cout<<s[i].fi<<" "<<s[i].se<<hvie;
rep(j,1,m){
int l=0,r=0;
q[0]=0;
rep(i,1,n){
while(l<=r&&s[q[l]+1].se<=s[i].fi) l++;
if(l<=r) f[i][j]= f[q[l]][j-1] +s [q[l]+1].se-s[i].fi;
// cout<<i<<","<<j<<" "<<q[l]<<" "<<f[i][j]<<hvie;
while(l<=r&&f[i][j-1]+s[i+1].se >= f[q[r]][j-1]+s[q[r]+1].se) r--;
q[++r]=i;
}
}
sort(b+1,b+1+cnt,greater<ll>());
ll ans=0;
rep(i,2,cnt) b[i]+=b[i-1];
rep(i,1,m){
if(m-i>cnt)continue;
ans=max(ans,f[n][i]+b[m-i]);
}
cout<<ans<<hvie;
return 0;
}
H. Olefin
【题意】
给定一个长度为 n n n的01串,每次翻转一个两端为1的交替段,求连续翻转 k k k次的方案数。
1 ≤ n , k ≤ 6 × 1 0 5 1\leq n,k\leq 6\times 10^5 1≤n,k≤6×105
【思路】
还是不太会。
由连续多个0分隔开的01段实际上是相互独立的,那么我们只需要考虑下面的子问题:
- 包含 n n n个1的交替01段连续操作 k k k次的方案数
然后我们把每个段的答案用NTT合并EGF即可。
首先我们可以由一个Naive的思路:每次操作后会将01段分成三个独立的段,用分布累和来优化暴力DP可以做到 O ( n 3 ) O(n^3) O(n3)
然后据说通过打表可以发现
n
n
n个1的交替序列答案是:
(
n
+
k
n
−
k
)
∏
i
=
1
k
(
2
i
−
1
)
\binom{n+k}{n-k}\prod_{i=1}^k(2i-1)
(n−kn+k)i=1∏k(2i−1)
然后就可以线性预处理
O
(
n
log
2
n
)
O(n\log^2n)
O(nlog2n)合并了。
事实上由于合并长度不到串长的一半,而且分治NTT很快,就可以过了。
上面这个式子是有具体意义的:
- ( n + k n − k ) \binom{n+k}{n-k} (n−kn+k)表示在最终串中放 n − k n-k n−k个不相邻的1的方案数
- ∏ i = 1 k ( 2 i − 1 ) \prod_{i=1}^k(2i-1) ∏i=1k(2i−1)表示每一个操作 k k k次的终态对应的不同操作序列数,这个可以通过归纳法证明,具体如下:
k = 1 k=1 k=1 显然方案数为1
对于一个 k + 1 k+1 k+1次操作的终止态,考虑复原其所有可行的$k $次父态 ,只需证明这样的父态恰有 2 k + 1 2k+1 2k+1个
容易发现一个到父态的一个逆操作一定是:翻转一段两端为 0 0 0的 极长 的交替序列;如果不是极长,在复原的序列中会出现相邻的1
假设在序列中,我们在末尾添加一个额外的0,然后让每一个形如10 的对互相抵消,则剩下的序列只包含0,且每一个0(除了最后一个)恰好可以代表一段极长的逆操作序列
例如: 010000 转化为 0000 ,其合法的逆操作为 (1,3) (4,4) (5,5); 010010转化为 00 (最后一个0抵消掉了),其合法的逆操作为 (1,3) (4,4)
由此, k + 1 k+1 k+1轮操作的合法逆操作数为 2 n − 1 − 2 ( n − k − 1 ) = 2 k + 1 2n-1-2(n-k-1)=2k+1 2n−1−2(n−k−1)=2k+1 *,*即其父态个数
因此结论归纳成立 ,证毕。
【参考代码】 (std)
#include <bits/stdc++.h>
constexpr int P = 998244353;
std::vector<int> rev, roots{0, 1};
int power(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % P)
if (b & 1)
res = 1ll * res * a % P;
return res;
}
void dft(std::vector<int> &a) {
int n = a.size();
if (int(rev.size()) != n) {
int k = __builtin_ctz(n) - 1;
rev.resize(n);
for (int i = 0; i < n; ++i)
rev[i] = rev[i >> 1] >> 1 | (i & 1) << k;
}
for (int i = 0; i < n; ++i)
if (rev[i] < i)
std::swap(a[i], a[rev[i]]);
if (int(roots.size()) < n) {
int k = __builtin_ctz(roots.size());
roots.resize(n);
while ((1 << k) < n) {
int e = power(3, (P - 1) >> (k + 1));
for (int i = 1 << (k - 1); i < (1 << k); ++i) {
roots[2 * i] = roots[i];
roots[2 * i + 1] = 1ll * roots[i] * e % P;
}
++k;
}
}
for (int k = 1; k < n; k *= 2) {
for (int i = 0; i < n; i += 2 * k) {
for (int j = 0; j < k; ++j) {
int u = a[i + j];
int v = 1ll * a[i + j + k] * roots[k + j] % P;
int x = u + v;
if (x >= P)
x -= P;
a[i + j] = x;
x = u - v;
if (x < 0)
x += P;
a[i + j + k] = x;
}
}
}
}
void idft(std::vector<int> &a) {
int n = a.size();
std::reverse(a.begin() + 1, a.end());
dft(a);
int inv = power(n, P - 2);
for (int i = 0; i < n; ++i)
a[i] = 1ll * a[i] * inv % P;
}
struct Poly {
std::vector<int> a;
Poly() {}
Poly(int a0) {
if (a0)
a = {a0};
}
Poly(const std::vector<int> &a1) : a(a1) {
while (!a.empty() && !a.back())
a.pop_back();
}
int size() const {
return a.size();
}
int operator[](int idx) const {
if (idx < 0 || idx >= size())
return 0;
return a[idx];
}
Poly mulxk(int k) const {
auto b = a;
b.insert(b.begin(), k, 0);
return Poly(b);
}
Poly modxk(int k) const {
k = std::min(k, size());
return Poly(std::vector<int>(a.begin(), a.begin() + k));
}
Poly divxk(int k) const {
if (size() <= k)
return Poly();
return Poly(std::vector<int>(a.begin() + k, a.end()));
}
friend Poly operator+(const Poly a, const Poly &b) {
std::vector<int> res(std::max(a.size(), b.size()));
for (int i = 0; i < int(res.size()); ++i) {
res[i] = a[i] + b[i];
if (res[i] >= P)
res[i] -= P;
}
return Poly(res);
}
friend Poly operator-(const Poly a, const Poly &b) {
std::vector<int> res(std::max(a.size(), b.size()));
for (int i = 0; i < int(res.size()); ++i) {
res[i] = a[i] - b[i];
if (res[i] < 0)
res[i] += P;
}
return Poly(res);
}
friend Poly operator*(Poly a, Poly b) {
int sz = 1, tot = a.size() + b.size() - 1;
while (sz < tot)
sz *= 2;
a.a.resize(sz);
b.a.resize(sz);
dft(a.a);
dft(b.a);
for (int i = 0; i < sz; ++i)
a.a[i] = 1ll * a[i] * b[i] % P;
idft(a.a);
return Poly(a.a);
}
Poly &operator+=(Poly b) {
return (*this) = (*this) + b;
}
Poly &operator-=(Poly b) {
return (*this) = (*this) - b;
}
Poly &operator*=(Poly b) {
return (*this) = (*this) * b;
}
Poly deriv() const {
if (a.empty())
return Poly();
std::vector<int> res(size() - 1);
for (int i = 0; i < size() - 1; ++i)
res[i] = 1ll * (i + 1) * a[i + 1] % P;
return Poly(res);
}
Poly integr() const {
if (a.empty())
return Poly();
std::vector<int> res(size() + 1);
for (int i = 0; i < size(); ++i)
res[i + 1] = 1ll * a[i] * power(i + 1, P - 2) % P;
return Poly(res);
}
Poly inv(int m) const {
Poly x(power(a[0], P - 2));
int k = 1;
while (k < m) {
k *= 2;
x = (x * (2 - modxk(k) * x)).modxk(k);
}
return x.modxk(m);
}
Poly log(int m) const {
return (deriv() * inv(m)).integr().modxk(m);
}
Poly exp(int m) const {
Poly x(1);
int k = 1;
while (k < m) {
k *= 2;
x = (x * (1 - x.log(k) + modxk(k))).modxk(k);
}
return x.modxk(m);
}
Poly sqrt(int m) const {
Poly x(1);
int k = 1;
while (k < m) {
k *= 2;
x = (x + (modxk(k) * x.inv(k)).modxk(k)) * ((P + 1) / 2);
}
return x.modxk(m);
}
Poly mulT(Poly b) const {
if (b.size() == 0)
return Poly();
int n = b.size();
std::reverse(b.a.begin(), b.a.end());
return ((*this) * b).divxk(n - 1);
}
std::vector<int> eval(std::vector<int> x) const {
if (size() == 0)
return std::vector<int>(x.size(), 0);
const int n = std::max(int(x.size()), size());
std::vector<Poly> q(4 * n);
std::vector<int> ans(x.size());
x.resize(n);
std::function<void(int, int, int)> build = [&](int p, int l, int r) {
if (r - l == 1) {
q[p] = std::vector<int>{1, (P - x[l]) % P};
} else {
int m = (l + r) / 2;
build(2 * p, l, m);
build(2 * p + 1, m, r);
q[p] = q[2 * p] * q[2 * p + 1];
}
};
build(1, 0, n);
std::function<void(int, int, int, const Poly &)> work = [&](int p, int l, int r, const Poly &num) {
if (r - l == 1) {
if (l < int(ans.size()))
ans[l] = num[0];
} else {
int m = (l + r) / 2;
work(2 * p, l, m, num.mulT(q[2 * p + 1]).modxk(m - l));
work(2 * p + 1, m, r, num.mulT(q[2 * p]).modxk(r - m));
}
};
work(1, 0, n, mulT(q[1].inv(n)));
return ans;
}
};
constexpr int N = 600000;
int fac[N + 1], invfac[N + 1], inv2[N + 1];
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n, k;
std::cin >> n >> k;
fac[0] = 1;
inv2[0] = 1;
for (int i = 1; i <= N; i++) {
fac[i] = 1LL * fac[i - 1] * i % P;
inv2[i] = 1LL * inv2[i - 1] * (P + 1) / 2 % P;
}
invfac[N] = power(fac[N], P - 2);
for (int i = N; i > 0; i--) {
invfac[i - 1] = 1LL * invfac[i] * i % P;
}
std::string s;
std::cin >> s;
std::vector<int> pos;
pos.reserve((n + 1) / 2);
for (int i = 0; i < n; i++) {
if (s[i] == '1') {
pos.push_back(i);
}
}
std::vector<int> len;
n = pos.size();
for (int l = 0, r; l < n; l = r) {
for (r = l + 1; r < n && pos[r - 1] + 2 == pos[r]; r++)
;
len.push_back(r - l);
}
if (k > n) {
std::cout << "0\n";
return 0;
}
auto solve = [&](auto self, int l, int r) -> Poly {
if (r - l == 1) {
int m = len[l];
std::vector<int> a(m + 1);
for (int i = 0; i <= m; i++) {
a[i] = 1LL * fac[m + i] * invfac[m - i] % P * invfac[i] % P * invfac[i] % P * inv2[i] % P;
}
return Poly(a);
}
int m = (l + r) / 2;
return self(self, l, m) * self(self, m, r);
};
std::cout << 1LL * solve(solve, 0, len.size())[k] * fac[k] % P << "\n";
return 0;
}
I. Penguins
【题意】
两个 20 × 20 20\times 20 20×20的01矩阵,要求第一个矩阵中的企鹅从 ( 20 , 20 ) (20,20) (20,20)移动到 ( 1 , 20 ) (1,20) (1,20),第二个矩阵中的企鹅从 ( 20 , 1 ) (20,1) (20,1)移动到 ( 1 , 1 ) (1,1) (1,1),两个企鹅的移动方式是关于y轴镜像的。你可以操纵第一个矩阵中的企鹅,求最短的字典序最小按键顺序,
【思路】
记一个四维的状态表示最短路,并顺便记录下怎么来的,然后bfs模拟即可。
【参考代码】
#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;
typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=2e5+10,M=22,mod=1e9+7,inf=0x3f3f3f3f;
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
return f?ret:-ret;
}
int las[N],dis[N];
char mp[2][M][M];
int id[M][M][M][M];
stack<char>st;
int dya[]={0,-1,1,0},dxa[]={1,0,0,-1};
int dyb[]={0,1,-1,0},dxb[]={1,0,0,-1};
struct Tpos
{
int xa,ya,xb,yb;
Tpos(int xa=0,int ya=0,int xb=0,int yb=0):xa(xa),ya(ya),xb(xb),yb(yb){}
};
queue<Tpos>q;
int getid(int a,int b,int c,int d)
{
return id[a][b][c][d];
}
int getidp(const Tpos&x)
{
return getid(x.xa,x.ya,x.xb,x.yb);
}
bool inmp(int x,int y,int op)
{
return 1<=x && x<=20 && 1<=y && y<=20 && mp[op][x][y]=='.';
}
void print()
{
//puts("print!");
int goal=getidp(Tpos(1,20,1,1)),ans=0;
do
{
int xa=goal/21/21/21,ya=goal/21/21%21,xb=goal/21%21,yb=goal%21;
mp[0][xa][ya]=mp[1][xb][yb]='A';
if(las[goal]!=-1)
{
++ans;
int nxa=las[goal]/21/21/21,nya=las[goal]/21/21%21;
if(nxa==xa && nya==ya)
{
int nxb=las[goal]/21%21,nyb=las[goal]%21;
if(nxb==xb)
{
if(nyb==yb+1) st.push('R');
else st.push('L');
}
else
{
if(nxb==xb+1) st.push('U');
else st.push('D');
}
}
else
{
if(nxa==xa)
{
if(nya==ya+1) st.push('L');
else st.push('R');
}
else
{
if(nxa==xa+1) st.push('U');
else st.push('D');
}
}
}
goal=las[goal];
}while(las[goal]!=-1);
printf("%d\n",ans);
while(!st.empty()) putchar(st.top()),st.pop();
puts("");
mp[0][20][20]=mp[1][20][1]='A';
for(int i=1;i<=20;++i)
{
printf("%s %s\n",mp[0][i]+1,mp[1][i]+1);
}
}
void bfs()
{
Tpos p=Tpos(20,20,20,1),goal=Tpos(1,20,1,1);
int tp=getidp(p),tgoal=getidp(goal);
memset(dis,-1,sizeof(dis));memset(las,-1,sizeof(las));
dis[tp]=0;
q.push(p);
while(!q.empty())
{
p=q.front();tp=getidp(p);q.pop();
// printf("%d %d %d %d\n",p.xa,p.ya,p.xb,p.yb);
if(tp==tgoal)
{
print();
return;
}
for(int i=0;i<4;++i)
{
int nxa=p.xa+dxa[i],nya=p.ya+dya[i],nxb=p.xb+dxb[i],nyb=p.yb+dyb[i];
if(!inmp(nxa,nya,0)) nxa=p.xa,nya=p.ya;
if(!inmp(nxb,nyb,1)) nxb=p.xb,nyb=p.yb;
Tpos np=Tpos(nxa,nya,nxb,nyb);
//printf("test:%d %d %d %d\n",nxa,nya,nxb,nyb);
int tnp=getidp(np);
if(dis[tnp]==-1)
{
// printf("%d\n",++cnt);
dis[tnp]=dis[tp]+1;
las[tnp]=tp;
q.push(np);
}
if(tnp==tgoal)
{
print();
return;
}
}
}
//puts("!!");
}
signed main()
{
for(int i=1;i<=20;++i)
{
scanf("%s",mp[0][i]+1);
scanf("%s",mp[1][i]+1);
}
for(int a=1;a<=20;++a) for(int b=1;b<=20;++b)
for(int c=1;c<=20;++c) for(int d=1;d<=20;++d)
id[a][b][c][d]=a*21*21*21+b*21*21+c*21+d;
bfs();
return 0;
}
J. Product of GCDs
【题意】
给定一个集合,求它所有大小为 k k k的子集的gcd的积,对 P P P取模,有 T T T组数据
T ≤ 60 , k ≤ 30 , P ≤ 1 0 14 T\leq 60,k\leq 30,P\leq 10^{14} T≤60,k≤30,P≤1014,数字不超过 8 × 1 0 4 8\times 10^4 8×104
【思路】
考虑对于每一个质因子分别统计答案,计算 f [ p ] [ c ] f[p][c] f[p][c]表示至少包含 p c p^c pc的数的个数,那么这里方案数是 ( f [ p ] [ c ] k ) \binom{f[p][c]} {k} (kf[p][c]),直接对于差分累加即可。注意这里计算得到的答案是一个指数,最后要做快速幂,所以我们这里要对 φ ( p ) \varphi(p) φ(p)取模,求这个东西可以Miller-Robin啥的求一下。
这里求出了至少的,再做一个 O ( x log x ) O(x\log x) O(xlogx)的容斥就行。
容斥还需要 O ( x log x ) O(\frac x {\log x}) O(logxx)次快速幂,所以统计答案是 O ( x ) O(x) O(x)的。
比赛队友写的不是这个写法,但大体上还是考虑每个质因子的贡献,卡常倒是卡了半天。
【参考代码】
#include<bits/stdc++.h>
#define int long long
// #define int __int128
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh() {
int ret = 0; bool f = 0; char c = getchar();
while (!isdigit(c)) {
if (c == EOF)return -1;
if (c == '-')f = 1;
c = getchar();
}
while (isdigit(c))ret = (ret << 3) + (ret << 1) + (c ^ 48), c = getchar();
return f ? -ret : ret;
}
const int maxn = 3e5 + 5;
int n, k, mod;
ll prime[99999], cnt;
ll gcd(ll a, ll b) {
if (!b) return a;
return gcd(b, a % b);
}
ll mmul(ll x, ll y, ll mod) { //快速加
ll ans = 0;
while (y) {
if (y % (1ll * 2)) ans = (ans + x % mod) % mod;
y /= (1ll * 2);
x = (x % mod + x % mod) % mod;
}
return ans;
}
ll fast_pow(ll x, ll y, ll m) { //快速幂
ll ans = 1;
x %= m;
while (y) {
if (y % 2)
ans = mmul(ans, x, m);
y /= 2;
x = mmul(x, x, m);
}
return ans;
}
bool MR(ll n) {
if (n == 2) return 1;
if (n < 2 || !(n & 1)) return 0;
ll m = n - 1;
ll k = 0;
while ((k & 1) == 0) {
k++;
m /= 2;
}
for (int i = 0; i < 12; i++) {
ll a = rand() % (n - 1) + 1;
ll x = fast_pow(a, m, n);
ll y = 0;
for (int j = 0; j < k; j++) {
y = mmul(x, x, n);
if (y == 1 && x != 1 && x != n - 1) return 0;
x = y;
}
if (y != 1) return 0;
}
return 1;
}
ll rho(ll n, ll c) { //找因子
ll i = 1, k = 2;
ll x = rand() % (n - 1) + 1;
ll y = x;
while (1) {
i++;
x = (mmul(x, x, n) + c) % n;
ll d = gcd(((y - x) + n) % n, n) % n;
if (d > 1 && d < n) return d;
if (y == x) return n;
if (i == k) { //一个优化,我TM也不知道为啥
y = x;
k <<= 1;
}
}
}
void find(ll n, ll c) { //分解的递归过程
if (n == 1) return;
if (MR(n)) {
prime[++cnt] = n;
return;
}
ll p = n;
ll k = c;
while (p >= n) p = rho(n, c--);
find(n / p, c);
find(p, c);
}
ll getphi(ll n) {
cnt = 0;
find(n, 120);
sort(prime + 1, prime + cnt + 1);
ll t = unique(prime + 1, prime + cnt + 1) - prime - 1;
double ans = n;
for (int i = 1; i <= t; i++) ans *= (1.0 - (1.0 / (double)prime[i]));
return ans;
}
struct num {
int x; bool bg;
};
int P;
num operator+(const num &a, const num &b) {
bool bg = 0;
if (a.bg || b.bg || a.x + b.x >= P) bg = 1;
return {(a.x + b.x) % P, bg};
}
// num mul(num x,num b){
// num ans={0,0};
// while(b.x>0){
// if(b.x&1) ans=(ans+x);
// x=(x+x);
// b={b.x/2,b.bg};
// }
// return ans;
// }
num mul(num a, num b) {
__int128 A = a.x, B = b.x, C = A * B;
return num{(ll)(C % P), a.bg || b.bg || C >= P};
}
inline ll mul(ll a, ll b, ll p) {
if (p <= 1000000000) return a * b % p;
else if (p <= 1000000000000ll) return (((a * (b >> 20) % p) << 20) + (a * (b & ((1 << 20) - 1)))) % p;
else {
ll d = (ll)floor(a * (long double)b / p + 0.5);
ll ret = (a * b - d * p) % p;
if (ret < 0) ret += p;
return ret;
}
}
int mmmul(int x, int b, int mod) {
int ans = 0;
while (b) {
if (b & 1) ans = (ans + x) % mod;
x = (x + x) % mod;
b >>= 1;
}
return ans;
}
void _fac(int x, map<int, vector<int>> &tms) {
for (int i = 2; i * i <= x; i++)if (x % i == 0) {
int tmp = 0;
while (x % i == 0) {
tmp++;
x /= i;
}
tms[i].pb(tmp);
}
if (x > 1)tms[x].pb(1);
}
int ksm(int x, int p, int mod) {
int ans = 1;
while (p) {
if (p & 1) ans = mul(ans, x, mod) % mod;
x = mul(x, x, mod) % mod;
p >>= 1;
}
return ans;
}
int phi(int x) {
int ans = x;
for (int i = 2; i * i <= x; i++)if (x % i == 0) {
ans /= i; ans *= (i - 1);
while (x % i == 0)x /= i;
}
if (x > 1) ans = ans / x * (x - 1);
return ans;
}
num C[maxn][31];
int prm[maxn], TOT = 0, minp[maxn]; bool vis[maxn];
void euler(int n) {
for (int i = 2; i <= n; i++) {
if (!vis[i]) prm[++TOT] = i, minp[i] = i;
for (int j = 1; j <= TOT && prm[j]*i <= n; j++) {
vis[i * prm[j]] = 1;
minp[i * prm[j]] = min(minp[i], prm[j]);
//if (i % prm[j] == 0)break;
}
}
}
vector<pii> _FAC(int x) {
map<int, int>t;
while (x > 1) {
t[minp[x]]++;
x /= minp[x];
}
vector<pii>ret;
for (auto &p : t) {
ret.pb({p.fi, p.se});
}
return ret;
}
vector<pii>yin[maxn];
map<int, vector<int>>tms;
signed main() {
// freopen("my.in","r",stdin);
euler(1e5);
rep(i, 1, 8e4) {
yin[i] = _FAC(i);
}
dwn(_, yh(), 1) {
tms.clear();
n = yh(); k = yh(); mod = yh();
P = getphi(mod);
C[0][0] = {1, 1 >= P};
// cerr<<"A";
rep(i, 1, n) {
C[i][0] = {1, 1 >= P};
rep(j, 1, min(k, i)) {
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]);
}
}
// cerr<<"B";
rep(i, 1, n) {
int x = yh();
for (auto &p : yin[x]) {
tms[p.fi].pb(p.se);
}
// _FAC(x,tms);
// _fac(x,tms);
}
// cerr<<"C";
int ans = 1;
// cerr<<clock()<<endl;
for (auto &i : tms) {
int l, r, up;
vector<int> &v = i.se;
sort(v.begin(), v.end());
num tot = {0, 0};
for (l = 0, up = v.size(); l < up; l = r + 1) {
r = l;
while (r + 1 < up && v[r + 1] == v[l]) r++;
int t = v[l], len = r - l + 1;
int gt = up - r - 1;
num tmp = {0, 0};
rep(o, 1, min(k, len)) {
int other = k - o;
tmp = tmp + mul(C[len][o], C[gt][other]);
}
tmp = mul(tmp, num{t, t >= P});
tot = (tot + tmp);
}
if (tot.bg) ans = mul(ans, ksm(i.fi, tot.x + P, mod), mod) % mod;
else ans = mul(ans, ksm(i.fi, tot.x, mod), mod) % mod;
}
// cerr<<clock()<<endl;
// cerr<<"D";
ll A = ans % mod;
printf("%lld\n", A);
//cerr<<"!\n";
}
return 0;
}
/*
1
10 4 3266841504299
23340 78378 42822 50742 19386 53374 24139 25166 65404 33240
*/
K. Stack
【题意】
已知一个序列若干时刻单调栈(栈顶最大)的大小,构造一个合法的序列。
【思路】
对于一个没有给定单调栈大小的位置,我们一定是一直往栈顶放元素不弹出,这样一定能构造出一组合法解;如果弹出,考虑[1,?,3]这样的序列就知道不一定合法了。
接着我们可以根据单调栈元素的变化构造出一系列拓扑关系,直接拓扑排序就可以得到一组合法解了。
【参考代码】
/*
* @date:2021-07-19 13:25:08
* @source:
*/
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {
return y > x ? x = y, 1 : 0;
}
template<class T, class G> bool chkMin(T &x, G y) {
return y < x ? x = y, 1 : 0;
}
const int MAXN = 1e6 + 5;
int N, K;
int B[MAXN];
vector<pii> v;
stack<int>st;
queue<int>q;
vector<int>G[MAXN];
int in[MAXN], num[MAXN];
void add(int x, int y) {
G[x].push_back(y); ++in[y];
//printf("%d %d\n",x,y);
}
void getans(int *a, int n) {
for (int i = 1; i <= n; ++i) {
if (a[i] > a[i - 1]) {
if (!st.empty()) add(i, st.top());
st.push(i);
} else {
int t = a[i - 1] - a[i] + 1;
while (t) {
add(st.top(), i);
st.pop(); --t;
}
if (!st.empty()) add(i, st.top());
st.push(i);
}
}
while (!st.empty()) {
int t = st.top();
st.pop();
if (st.empty()) break;
add(t, st.top());
}
for (int i = 1; i <= n; ++i) if (!in[i]) q.push(i);
int cnt = n;
while (!q.empty()) {
int x = q.front(); q.pop();
num[x] = cnt--;
for (auto v : G[x]) {
--in[v];
if (!in[v]) q.push(v);
}
}
for (int i = 1; i <= n; ++i) printf("%d ", num[i]);
}
int main() {
scanf("%d%d", &N, &K);
for (int i = 1, x, y; i <= K; ++i) {
scanf("%d%d", &x, &y);
B[x] = y;
v.push_back({x, y});
if (y > x) {
puts("-1");
return 0;
}
}
sort(ALL(v));
for (int i = 1; i < K; ++i) {
if (v[i].fir - v[i].sec < v[i - 1].fir - v[i - 1].sec) {
puts("-1");
return 0;
}
}
B[1] = 1;
for (int i = 1; i <= N; ++i) {
if (!B[i]) B[i] = B[i - 1] + 1;
}
getans(B, N);
return 0;
}
L. Wechat Walk
【题意】
给定 n n n个人之间的好友关系,每次单点增加一个人的步数,求每个人在自己列表里保持冠军的时间总长。
n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n≤2×105,保证一个人一天总共不会走超过 1 0 4 10^4 104步
【思路】
直接做不太好做,但是这里有个很特殊的权值值域 W = 10000 W=10000 W=10000,我们考虑分块。
设 S = n S=\sqrt n S=n,我们称度数 ≤ S \leq S ≤S的点为小点,度数 > S >S >S的点为大点
依次考虑每一个时刻,维护每个人成为冠军的区间。
假设被修改的点为 x x x,容易发现有以下情况:
- 如果 x x x原来是冠军,无需修改冠军情况
- 如果
x
x
x不是冠军,设原来权值为
a
a
a,新权值为
b
b
b,
a
<
b
a<b
a<b那么:
- x x x是小点:直接枚举更新所有和 x x x相邻的点,更新他们的冠军情况,那么也能同时知道 x x x是否成为了冠军
- x x x是大点:对于 x x x周围的大点,同样直接枚举进行更新;对于 x x x周围的小点,注意到 W W W比较小,因此可以直接暴力存下和 x x x相邻的小点中,权值为 w w w的且为冠军的点集 C x , w C_{x,w} Cx,w,成为冠军的时间只有在每次小点被增加的时候出现,那么 C C C的总元素个数就是 O ( n S ) O(nS) O(nS)的了。我们可以暴力扫描权值,判断 i ∈ ( a , b ] , C x , i i\in(a,b],C_{x,i} i∈(a,b],Cx,i中的点是否不再成为冠军,我们不需要维护 C C C的删除操作。不难发现,元素和集合不会重复被扫描。
总的复杂度是 O ( S W + n S ) O(SW+nS) O(SW+nS)的。
事实上 W W W无限制的时候也是可以做的,因为离散化以后仍然可以像上面这么做。
【参考代码】
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
int ret=0;bool f=0;char c=getchar();
while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
return f?-ret:ret;
}
const int maxn=2e5+5;
int main(){
int n=yh(),m=yh(),q=yh();
vector<vector<int>>adj(n+1),nb(n+1);
vector<vector<pii>>tick(10001);
vector<int>now(n+1,q),last(n+1,q),mn(n+1,q),ans(n+1,0),walks(n+1,0);
int SZ=sqrt(n);
rep(i,1,m){
int x=yh(),y=yh();
adj[x].pb(y);adj[y].pb(x);
}
rep(i,1,n){
if((int)adj[i].size()>=SZ){
for(auto &j:adj[i]){
nb[j].pb(i);
}
}
}
rep(i,1,q){
int x=yh(),val=yh();
walks[x]+=val;
tick[walks[x]].pb({x,i});
}
dwn(w,10000,1){
for(auto [x,t]:tick[w]){
for(auto &y:nb[x]){
mn[y]=min(mn[y],t);
}
last[x]=now[x];
now[x]=t;
}
for(auto [x,t]:tick[w]){
if((int)adj[x].size()<SZ){
int r=last[x];
for(auto y:adj[x]){
r=min(r,now[y]);
}
ans[x]+=max(0,r-t);
}
else{
ans[x]+=max(0,min(mn[x],last[x])-t);
}
}
}
rep(i,1,n) cout<<ans[i]<<"\n";
return 0;
}