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("");
		}
	}
}

混凝土粉末

题意:

维护两个操作:

  1. \([l,r]\) 的所有值加上一个数 \(w\)
  2. 回答 \(a_x \geq y\) 的第一个时间。

支持离线。


整体二分板子题,带 O2 跑得比带 O2 的波特快。

那就讲一讲实现里面的小坑点吧:

  1. Solve(int l,int r,int L,int R) 里面,如果 \(l=r\),要注意查询要比这个询问后出现才能更新答案;
  2. 这个题调试的时候可能会出现 cnt1, cnt2 的值发生灵异变化,需要小心;
  3. 每次处理完之后可以不改询问本体,直接保留当前的树状数组然后先处理右半部分也是可以的;
  4. 答案可以是 \(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\) 的度数,考虑其所有变化:

  1. \(u\) 的其他出边指向的点,流量增加了 \(\dfrac{f_u}{d(d-1)}\)
  2. \(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;
}
posted @ 2022-09-04 15:38  SyadouHayami  阅读(132)  评论(0编辑  收藏  举报

My Castle Town.