2022.9.4 模拟赛
For NOIP.
谜之阶乘
题意:问 \(n\) 能够被多少组 \((a,b)\) 表示,满足 \(n = \dfrac{a!}{b!}\)。\(n \leq 10^{18}\),多组询问。
首先猜测 \(a-b\) 并不大,然后就枚举这个差值然后二分暴力算判断就好了。为了防止溢出可以对 \(10^{18}+1\) 求最小值。
注意特判 \(n=1\)。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef __int128 LL;
typedef double DB;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
LL read()
{
LL x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(LL x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
void Solve();
int main(){
LL T=read();
while(T-->0) Solve();
return 0;
}
typedef pair<LL,LL> P;
#define mp make_pair
const LL inf=1e18;
LL n;
LL check(LL x,LL p)
{
LL ans=1;
for(LL i=x;i>x-p;--i)
{
ans*=i;
if(ans>inf) return inf+1;
}
return ans;
}
void Solve()
{
n=read();
if(n==1) return void(puts("-1"));
vector<P> Ans;
for(LL i=20;i;--i)
{
LL l=i+1,r=inf,ans=r+1;
while(l<=r)
{
LL mid=(l+r)>>1;
if(check(mid,i)<n) l=mid+1;
else if(check(mid,i)>n) r=mid-1;
else
{
ans=mid;
break;
}
}
if(ans<=inf) Ans.push_back(mp(ans,ans-i));
}
write(LL(Ans.size())),puts("");
for(auto st:Ans) write(st.first),putchar(' '),write(st.second),puts("");
}
子集
题意:将 \(1 \sim n\) 分成 \(k\) 组,每组包含的数相同,包含的数的和相同。给出无解信息或者求出一组分组。\(\sum n \leq 10^6\)。
首先如果和不是 \(k\) 的倍数或者 \(n=k\) 且 \(n \neq 1\),无解。注意要特判 \(n=k=1\)。
然后考虑接下来的构造。有个部分分是 \(2 \mid \dfrac{n}{k}\),这个没选的最大值和没选的最小值相互匹配,这样一定是对的。
问题在 \(2 \not\mid \dfrac{n}{k}\)。有一个比较直观的想法是 \(k\) 组第一个数放 \(1,2,\cdots k\),第二个数放 \(2k,k+1,k+2,\cdots ,2k-1\),以此类推,放到 \(k\) 个之后后面就可以用 \(2 \mid \dfrac{n}{k}\) 的方法解决问题。但是要求 \(k^2 \leq n\),也许没有 \(k^2 > n\) 的情况——?
\(n=15,k=5\) 等就是一组反例。那没办法啊,咋整啊。暴力吧。
注意到之前我们的方法都是 \(k\) 组第一个数放 \(1 \sim k\),第二个放 \(k+1 \sim 2k\),我们这次也来强制钦定一下。
这个限制很强,最终只有六组解:
1 8 15
2 9 13
3 10 11
4 6 14
5 7 12
1 8 15
2 10 12
3 7 14
4 9 11
5 6 13 *
1 9 14
2 7 15
3 10 11
4 8 12
5 6 13
1 9 14
2 10 12
3 6 15
4 7 13
5 8 11
1 10 13
2 7 15
3 9 12
4 6 14
5 8 11
1 10 13
2 8 14
3 6 15
4 9 11
5 7 12
注意到打 \(\star\) 的那一组。发现竖着看这个值排列的规律非常明显。对 \(n=21,k=7\) 验证一发这种构造,也是可以的。
于是我们只需要占用三列就好。这样就处理了所有 \(2 \not\mid \dfrac{n}{k}\) 的问题(这里 \(\dfrac{n}{k} \geq 3\),\(1\) 的情况早被我们判掉了)。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
int read()
{
int x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(int x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
void Solve();
int main(){
int T=read();
while(T-->0) Solve();
return 0;
}
int n,k;
vector<int> Ans[1000005];
void Solve()
{
n=read(),k=read();
{
LL C=LL(n)*LL(n+1);
C>>=1;
if(C%k) return void(puts("No"));
}
if(n==k)
{
if(n==1) puts("Yes\n1");
else puts("No");
return ;
}
puts("Yes");
int c=n/k;
if(c%2==0)
{
int d=0;
for(int i=1;i<=k;++i)
{
for(int j=1;j<=n/k/2;++j)
{
int p=++d,q=n-d+1;
write(p),putchar(' '),write(q),putchar(j==n/k/2?'\n':' ');
}
}
return ;
}
else
{
for(int i=0;i<k;++i) Ans[i].clear();
if(k>c)
{
for(int i=0;i<k;++i) Ans[i].push_back(i+1);
int d=k-1;
for(int i=0;i<k;++i)
{
Ans[d].push_back(i+k+1);
d-=2;
if(d<0) d+=k;
}
d=k-2;
for(int i=0;i<k;++i)
{
Ans[d].push_back(i+k+k+1);
d-=2;
if(d<0) d+=k;
}
d=0;
for(int i=0;i<k;++i)
{
for(int j=1;j<=((n/k)-3)/2;++j)
{
int p=3*k+(++d),q=n-d+1;
Ans[i].push_back(p),Ans[i].push_back(q);
}
}
for(int i=0;i<k;++i)
{
for(auto v:Ans[i]) write(v),putchar(' ');
puts("");
}
return ;
}
for(int i=0;i<k;++i) for(int j=0;j<k;++j) Ans[(i+j)%k].push_back(i*k+j+1);
int d=0;
for(int i=0;i<k;++i)
{
for(int j=1;j<=((n/k)-k)/2;++j)
{
int p=k*k+(++d),q=n-d+1;
Ans[i].push_back(p),Ans[i].push_back(q);
}
}
for(int i=0;i<k;++i)
{
for(auto v:Ans[i]) write(v),putchar(' ');
puts("");
}
}
}
混凝土粉末
题意:
维护两个操作:
- 将 \([l,r]\) 的所有值加上一个数 \(w\);
- 回答 \(a_x \geq y\) 的第一个时间。
支持离线。
整体二分板子题,带 O2 跑得比带 O2 的波特快。
那就讲一讲实现里面的小坑点吧:
- 在
Solve(int l,int r,int L,int R)
里面,如果 \(l=r\),要注意查询要比这个询问后出现才能更新答案; - 这个题调试的时候可能会出现
cnt1, cnt2
的值发生灵异变化,需要小心; - 每次处理完之后可以不改询问本体,直接保留当前的树状数组然后先处理右半部分也是可以的;
- 答案可以是 \(0\),这样赋特殊值的方法写起来会很麻烦。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
LL read()
{
LL x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(LL x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
struct Modify{
LL l,r,w,id;
Modify(LL L=0,LL R=0,LL W=0,LL ID=0){l=L,r=R,w=W,id=ID;}
}mdf[1000005];
struct Query{
LL x,y,id;
Query(LL X=0,LL Y=0,LL ID=0){x=X,y=Y,id=ID;}
}qry[1000005],seq1[1000005],seq2[1000005];
bool isq[1000005];
LL n,q;
inline LL lowbit(LL x){return x&(-x);}
struct BinaryIndexedTree{
LL tr[1000005];
void modify(LL x,LL c){for(LL i=x;i<=n;i+=lowbit(i)) tr[i]+=c;}
LL query(LL x){LL ans=0;for(LL i=x;i;i^=lowbit(i)) ans+=tr[i];return ans;}
void modify(LL l,LL r,LL c){modify(l,c),modify(r+1,-c);}
}bit;
LL ans[1000005];
void Solve(LL l,LL r,LL L,LL R)
{
if(L>R) return ;
if(l==r)
{
for(LL i=L;i<=R;++i) ans[qry[i].id]=(mdf[l].id>qry[i].id?0:mdf[l].id);
return ;
}
LL mid=(l+r)>>1;
LL i=l,j=L;
LL cnt1=0,cnt2=0;
while(i<=mid && j<=R)
{
if(mdf[i].id<qry[j].id)
{
bit.modify(mdf[i].l,mdf[i].r,mdf[i].w);
++i;
}
else
{
LL p=qry[j].x,w=qry[j].y;
LL c=bit.query(p);
if(c<w) seq2[++cnt2]=qry[j];
else seq1[++cnt1]=qry[j];
++j;
}
}
while(i<=mid)
{
bit.modify(mdf[i].l,mdf[i].r,mdf[i].w);
++i;
}
while(j<=R)
{
LL p=qry[j].x,w=qry[j].y;
LL c=bit.query(p);
if(c<w) seq2[++cnt2]=qry[j];
else seq1[++cnt1]=qry[j];
++j;
}
LL d=L;
for(LL p=1;p<=cnt1;++p) qry[d++]=seq1[p];
for(LL p=1;p<=cnt2;++p) qry[d++]=seq2[p];
Solve(mid+1,r,L+cnt1,R);
for(LL p=l;p<=mid;++p) bit.modify(mdf[p].l,mdf[p].r,-mdf[p].w);
Solve(l,mid,L,L+cnt1-1);
}
LL mc,qc;
int main(){
n=read(),q=read();
for(LL i=1;i<=q;++i)
{
LL op=read();
if(op==1)
{
LL L=read(),R=read(),W=read();
mdf[++mc]=Modify(L,R,W,i);
}
else
{
LL X=read(),Y=read();
qry[++qc]=Query(X,Y,i);
isq[i]=true;
}
}
mdf[++mc]=Modify(1,n,1e18,0);
Solve(1,mc,1,qc);
for(LL i=1;i<=q;++i) if(isq[i]) write(ans[i]),puts("");
return 0;
}
排水系统
题意:有一个 DAG,前 \(p\) 个源点初始流量为 \(1\),后 \(q\) 个点为汇点(每个非汇点至少有两条出边,非源点存在入边)。现在每条边有一定概率断掉,问最终汇点汇聚的流量的期望值。
因为这个 DAG 会发生删边,这很不巧妙啊。
我们考虑用点和边来分担/提供流量就好了,同时保证图不变即可。
定义 \(f_u,g_u\) 分别表示不删边时到 \(u\) 的流量,和 \(u\) 上面已经经历了删边的到 \(u\) 的流量。
比如现在删掉了 \(u \to v\) 这条边。记 \(d\) 为删边前 \(u\) 的度数,考虑其所有变化:
- \(u\) 的其他出边指向的点,流量增加了 \(\dfrac{f_u}{d(d-1)}\);
- \(v\) 的流量减少了 \(\dfrac{f_u}{d}\)。
新加一些边去除这些影响。欲让 \(u\) 到其他点的流量增加到 \(\dfrac{f_u}{d-1}\),只需要让 \(u\) 的流量增加 \(\dfrac{f_u}{d-1}\);同时 \(v\) 的流量多出了 \(\dfrac{f_u}{d}+\dfrac{f_u}{d(d-1)}\) 需要减掉。
剩下的可以直接用转移 \(f\) 的方式转移 \(g\),这样肯定是对的,因为 DAG 的基本形态并没有改变,多出来或少出来的权值已经被我们通过巧妙的手段规避了。
具体转移细节可以看代码。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<18,stdin),p1==p2)?EOF:*p1++)
int read()
{
int x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(int x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int MOD=998244353;
inline int Add(int u,int v){return u+v>=MOD?u+v-MOD:u+v;}
inline int Sub(int u,int v){return u-v>=0?u-v:u-v+MOD;}
inline int Mul(int u,int v){return LL(u)*LL(v)%MOD;}
inline int add(int &u,int v){return u=Add(u,v);}
inline int sub(int &u,int v){return u=Sub(u,v);}
inline int mul(int &u,int v){return u=Mul(u,v);}
int QuickPow(int x,int p=MOD-2)
{
int ans=1,base=x;
while(p)
{
if(p&1) mul(ans,base);
mul(base,base);
p>>=1;
}
return ans;
}
typedef pair<int,int> P;
#define mp make_pair
int n,p,q,m;
int oishi;
vector<P> G[200005];
int deg[200005];
int out[200005];
int f[200005],g[200005];
/*
f: 不断边的值。
g: 期望。
*/
void topSort()
{
queue<int> Q;
for(int i=1;i<=n;++i) if(!deg[i]) Q.push(i);
for(int i=1;i<=p;++i) f[i]=g[i]=1;
while(!Q.empty())
{
int u=Q.front();
Q.pop();
int d=int(G[u].size()),invd=QuickPow(d),invdp=QuickPow(d-1);
for(auto st:G[u])
{
int v=st.first,w=st.second;
if(!--deg[v]) Q.push(v);
add(f[v],Mul(f[u],invd));
add(g[v],Mul(g[u],invd));
add(g[v],Mul(Mul(Mul(oishi,out[u]),Mul(f[u],invdp)),invd));
sub(g[v],Mul(Mul(oishi,w),Mul(f[u],Add(invd,Mul(invd,invdp)))));
}
}
}
int main(){
n=read(),p=read(),q=read(),m=read();
for(int i=1;i<=m;++i)
{
int u=read(),v=read(),w=read();
G[u].push_back(mp(v,w));
++deg[v];
add(out[u],w);
add(oishi,w);
}
oishi=QuickPow(oishi);
topSort();
for(int i=n-q+1;i<=n;++i) write(g[i]),putchar(i==n?'\n':' ');
return 0;
}