CSP-S模拟2(联考) 谜之阶乘 子集 混凝土粉末 排水系统

rank 40 40多分?

T1:暴力;T2:数学构造;T3:数据结构;T4:概率期望

T1

image

T2:构造出(1--n)的连续整数分成k组,每组的数加起来一样。(n<=1e6)

image
只要能实现一种构造方案,使得3k个连续数字分k组可以达到(a+b+c)相同(或2k,很显然)
构造方法:
1 8 15
2 9 13
3 10 11
4 6 14
5 7 12
很玄学的构造方式,积累下来!!

点击查看代码






#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rint register int
#define chu printf
#define _f(i,a,b) for(rint i=a;i<=b;++i)
#define f_(i,a,b)  for(rint i=a;i>=b;--i)
inline ll re()
{
	ll x=0,h=1;char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')h=-1;ch=getchar();
	}
	while(ch<='9'&&ch>='0')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*h;
}
const int N=0;
int n,k,T;
ll val,ev;//ev:每组的加和是多少
//val:一共的值,k:一共几组
vector<int>g[1000000+10];//k是多少就开多少
inline void special_deal()
{
	int cnt=n/k;
	int to2=cnt%3,to3;
	if(to2==1)to2+=3,to3=cnt-to2;
	else if(!to2)to3=cnt;
	//1 999957 333319

	else if(to2==2)to3=cnt-2;//3和2的都分多少组
	//先分3的
	to3/=3;int tiao=3*k;ll sumbase=((ll)3*k+1)*((ll)3*k)/2/k;
	//chu("to3:%d\n",to3);
	_f(i,1,to3)
	{
		int sta=tiao*(i-1);
		_f(j,1,k)
		g[j].push_back(++sta);//第一列放顺序
		_f(j,k/2+2,k)
		g[j].push_back(++sta);
		_f(j,1,k/2+1)
		g[j].push_back(++sta);
		ll sum=sumbase+tiao*3*(i-1);//3个一组每组的和
		_f(j,1,k)
		{
			int pos=g[j].size()-1;
			g[j].push_back(sum-(ll)g[j][pos]-(ll)g[j][pos-1]);
		//	chu("insert:%lld\n",sum-(ll)g[j][pos]-(ll)g[j][pos-1]);
		}
	}
	if(!to2)return;
	int l=k*to3*3+1,r=n;
	to3=to3*k*3+1;
	//chu("l:%d r:%d\n",l,r);
	while(1)
	{
		_f(i,1,k)
		g[i].push_back(l),g[i].push_back(n-l+to3),++l;;
		to2-=2;
		if(!to2)break;
	}
}
int main()
{
	//freopen("b.in","r",stdin);
	//freopen(""."w",stdout);
	T=re();
	while(T--)
	{
		_f(i,1,k)g[i].clear();
		n=re(),k=re();
		if(k==1)
		{
			chu("Yes\n");
			_f(i,1,n)chu("%d ",i);
			chu("\n");
			continue;
		}
		if(k==n)
		{
			chu("No\n");continue;
		}
		val=(1+n)*(ll)n/2;
		ev=val/k;int cnt=n/k;//cnt是每组多少个
		if(ev*k!=val)
		{
			chu("No\n");continue;
		}
		if(ev<n)
		{
			chu("No\n");continue;
		}
		if(!(cnt&1))
		{
			chu("Yes\n");
			int pos=0;
			_f(i,1,k)//第几组
			{
				_f(j,1,cnt/2)//每组几个
				{
					++pos;
					chu("%d %d ",pos,n-pos+1);
				}
				chu("\n");
			}
		}
		else
		{
			special_deal();
			chu("Yes\n");
			_f(i,1,k)
			{
				for(rint to:g[i])
				chu("%d ",to);
				chu("\n");
			}
		}
	}
	return 0;
}
/*
20
1 1
18 3
15 3
15 5
9 3
13 1
4 4
12 1
8 4
5 1
11 1
11 11
8 8
3 1
12 4
6 1
16 8
14 14
4 1
12 2
*/

T3:给出一个二维坐标系,支持在(l,r)区间放上h块颜色为num_operator(当前操作编号)的砖块,每块高度1。支持(x,y)询问位置的砖块颜色.(n<=1e6,h<=1e18)

问题转化:支持在一维序列加上某多少数,并且询问使得x位置的数到达>=y的最小的操作编号。

方法一:可持久化线段树维护x轴意义上的点和值(区间修改,点查询)
在查询时二分找到最早的使得pos位置值>=y的操作
\(O(n*logn*logn)\)

点击查看代码




#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rint register int
#define chu printf
#define _f(i,a,b) for(rint i=a;i<=b;++i)
#define f_(i,a,b)  for(rint i=a;i>=b;--i)
inline ll re()
{
	ll x=0,h=1;char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')h=-1;ch=getchar();
	}
	while(ch<='9'&&ch>='0')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*h;
}
const int N=0;
int ls[1000000*40+100],root[1000000+100],tot,rs[1000000*40+100];
ll sum[1000000*40+100];
int n,q;int nct=0;
inline void Insert(int pre,int &rt,int L,int R,int l,int r,ll ad)
{
	//chu("dfs\n");
	//chu("%d %d %d %d\n",L,R,l,r);
	rt=++tot;
	ls[rt]=ls[pre];rs[rt]=rs[pre],sum[rt]=sum[pre];
	if(l<=L&&R<=r)
	{
		sum[rt]+=ad;return;
	}
	int mid=(L+R)>>1;
	if(l<=mid)Insert(ls[pre],ls[rt],L,mid,l,r,ad);
	if(r>mid)Insert(rs[pre],rs[rt],mid+1,R,l,r,ad);
}
inline ll Query(int rt,int l,int r,int pos)
{
	if(!rt)return 0;
	if(l==r)return sum[rt];
	int mid=(l+r)>>1;
	if(pos<=mid)return sum[rt]+Query(ls[rt],l,mid,pos);
	return sum[rt]+Query(rs[rt],mid+1,r,pos);
}
int main()
{
//	freopen("concrete3.in","r",stdin);
	//freopen("c.out","w",stdout);
	n=re(),q=re();
	_f(i,1,q)
	{
		int opt=re();
		if(opt==1)
		{
			int l=re(),r=re();ll ad=re();
			Insert(root[i-1],root[i],1,n,l,r,ad);
			//chu("opt=1\n");
		}
		else
		{
			root[i]=root[i-1];
			int x=re();ll y=re();
			int l=1,r=i-1;//可能的修改时间戳范围
			int ans=0;
			while(l<=r)
			{
				int mid=(l+r)>>1;
				if(Query(root[mid],1,n,x)>=y)
				{
					ans=mid;r=mid-1;
				}
				else l=mid+1;
			}
			chu("%d\n",ans);
			//chu("opt=2\n");
		}
	}
	return 0;
}
/*
5 8
1 1 4 2
2 3 1
2 3 3
1 2 5 1
2 3 3
2 5 2
2 1 2
2 1 3

1
0
4
0
1
0
*/

方法二:考虑离线

树状数组消除非法时间操作影响

把修改拆成二元组(id,val),表示时间戳和加减的值,比如(l,r,val),在l位置加二元组(opt,val),在r+1位置加上二元组(opt,-val)
然后按照x递增遍历,同时按照每个位置的二元组维护在操作编号上的树状数组,前缀和就表示累计到目前为止的修改直到本位置(x)的累计影响。在查询只需要二分树状数组。
\(O(n*logn*logn)\)但是它的空间很优秀!

点击查看代码




#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rint register int
#define chu printf
#define _f(i,a,b) for(rint i=a;i<=b;++i)
#define f_(i,a,b)  for(rint i=a;i>=b;--i)
inline ll re()
{
	ll x=0,h=1;char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')h=-1;ch=getchar();
	}
	while(ch<='9'&&ch>='0')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*h;
}
const int N=0;
int n,q;
struct node
{
	ll y;int id;int tim;
	node(){}
	node(ll yy,int iidd,int ttmm)
	{
		y=yy;id=iidd;tim=ttmm;
	}
};
vector< pair<ll,int> > g[1000000+10];//存每个位置的操作+时间戳
vector<node>qu[1000000+10];//存询问,但是,好像还有时间限制?
int tot,ans[1000000+10];
ll low[1000000+10];
#define lowbit(x)  (x&(-x))
inline void Insert(int x,ll val)
{
	while(x<=q)
	{
		low[x]+=val;
		x+=lowbit(x);
	}
}
inline ll Query(int x)
{
	ll ans=0;
	while(x)
	{
		ans+=low[x];
		x-=lowbit(x);
	}
	return ans;
}
int main()
{
	//freopen("concrete3.in","r",stdin);
//	freopen("c.out","w",stdout);
	n=re(),q=re();
	_f(i,1,q)
	{
		int opt=re();
		if(opt==1)
		{
			int l=re(),r=re();ll ad=re();
			g[l].push_back(make_pair(ad,i));
			//chu("%d(%d %d)\n",l,ad,i);
			g[r+1].push_back(make_pair(-ad,i));
		}
		else
		{
			int x=re();ll y=re();
			//chu("insert%d %d %d\n",y,tot+1,i);
			qu[x].push_back(node(y,++tot,i));//询问放进去,对应横坐标
		}
	}
	_f(i,1,n)//一个一个找
	{
		for(auto iop:g[i])
		{
			Insert(iop.second,iop.first);
			//chu("(%d)add(%d  %lld)\n",i,iop.second,iop.first);
			//chu("query;%d\n",Query(3));
		}
		for(auto ip:qu[i])
		{
			//在1~ip.tim的树状数组下标范围,找到>=ip.y的第一个位置,存到ans下标ip.id里面
			//树状数组维护的是询问的时间戳!
			if(Query(ip.tim)<ip.y)
			{
				ans[ip.id]=0;continue;
			}
			int l=1,r=ip.tim;int asns=0;
			//chu("(%d)%d~%d\n",i,l,r);
			//chu("%lld %d %d\n",ip.y,ip.id,ip.tim);
			while(l<=r)
			{
			//	chu("df\n");
				int mid=(l+r)>>1;
				if(Query(mid)>=ip.y)
				{
					r=mid-1;asns=mid;
				}
				else l=mid+1;
			}
			ans[ip.id]=asns;
		}
	}
	_f(i,1,tot)
	chu("%d\n",ans[i]);
	return 0;
}
/*
5 8
1 1 4 2
2 3 1
2 3 3
1 2 5 1
2 3 3
2 5 2
2 1 2
2 1 3

1
0
4
0
1
0
*/

T4:有一个有向无环图,初始入度是0的点的权值是1,其他都是0,正常情况下每个root会等概率的让“权值”流向所有相连的点。但是,对于每条边,都有\(ai/\sum_{k=1}^{egdecnt}ak\)的概率堵塞不能流通,对于一张图只会有1条边堵塞。求最后出度是0的点的权值期望(n<=2e5,edge_cnt<=5e5)

对于暴力,就是枚举删除每一条边,拓扑跑计算最后的权值,但是注意必须跑拓扑,不能dfs,因为rt只有被更新完全才可以继续传递,而且起点不一定是1~m,断边之后(u,v)的点还要继续遍历,只是价值不累加,不然就会wa。

优化:每次删边可以看成对(u,v)赋予初始权值

发现,对于删除(u,v)的边,等价于在val[u]+=[断边概率]*断边后其他点多流的val值,val[v]-=[断边概率] * ....。
然后再进行正常的不删边的权值计算。
推导:[断]=p
断后:\(val[v]=normalval*(1-p),val[other]=normalval*(1-p)+cutval*p] , cutval=x/(size-1),normalval-x/size\)
你进行通分合并,发现正好是正确的。

点击查看代码


#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define rint register int
#define chu printf
#define _f(i,a,b) for(rint i=a;i<=b;++i)
#define f_(i,a,b)  for(rint i=a;i>=b;--i)
inline ll re()
{
	ll x=0,h=1;char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')h=-1;ch=getchar();
	}
	while(ch<='9'&&ch>='0')
	{
		x=(x<<1)+(x<<3)+(ch^48);
		ch=getchar();
	}
	return x*h;
}
const int N=0;const ll mod=998244353;
int head[200000+10],cd[200000+10],rd[200000+10],tot,n,m,r,k;
ll ny[200000+10],wat[200000+10],dwat[200000+10],sdam,dp;
int sta[200000+10],top;
struct node
{
	int to,fr,nxt;
	ll dam;
}e[500000+100];
inline void Add(int u,int v,int da)
{
	cd[u]++;//出边
	rd[v]++;
	e[++tot].to=v;e[tot].nxt=head[u];head[u]=tot;e[tot].fr=u;e[tot].dam=da;
}
inline ll qpow(ll a,ll b)
{
	ll ads=1;
	while(b)
	{
		if(b&1)ads=ads*a%mod;
		b>>=1;a=a*a%mod;
	}
	return ads;
}
int main()
{
	//freopen("water5.in","r",stdin);
	//freopen("d.out","w",stdout);
	n=re(),m=re(),r=re(),k=re();
	_f(i,1,k)
	{
		int xi=re(),yi=re(),ai=re();sdam+=ai;Add(xi,yi,ai);
		if(sdam>mod)sdam-=mod;
	}
	//chu("dsa:%lld\n",sdam);
	ny[0]=ny[1]=1;
	_f(i,2,n)
	ny[i]=(mod-mod/i)*ny[mod%i]%mod;
	dp=qpow(sdam,mod-2);//逆元
	_f(i,1,m)sta[++top]=i,wat[i]=1;
	int gen=0;
	//chu("dfds\n");
	while(top<n)
	{
		//chu("top:%d\n",top);
		++gen;
		for(rint i=head[sta[gen]];i;i=e[i].nxt)
		{
			int to=e[i].to;
			rd[to]--;
			if(!rd[to])sta[++top]=to;
		}
	}
	_f(ir,1,top)
	{
		int i=sta[ir];
		if(!cd[i])continue;
		ll giv=wat[i]*ny[cd[i]]%mod;
		ll lgiv=wat[i]*ny[cd[i]-1]%mod;
		for(rint j=head[i];j;j=e[j].nxt)
		{
			int to=e[j].to;
			wat[to]=(wat[to]+giv)%mod;
			dwat[i]=(dwat[i]+lgiv*e[j].dam%mod*dp%mod)%mod;
			dwat[to]=(dwat[to]+(mod-lgiv%mod)*e[j].dam%mod*dp%mod)%mod;
		}
	}
	_f(i,1,m)dwat[i]+=1;
	_f(ir,1,top)
	{
		int i=sta[ir];
		if(!cd[i])continue;
		ll giv=dwat[i]*ny[cd[i]]%mod;
		for(rint j=head[i];j;j=e[j].nxt)
		{
			int to=e[j].to;
			dwat[to]=(dwat[to]+giv)%mod;
		}
	}
	_f(i,n-r+1,n)chu("%lld ",dwat[i]);
	return 0;
}
/*
*/

总结一下:其实T1很好想,但是还是“暴力枚举”的固定思维模式没有脱离,直接就很离谱的暴力搜索起点,没有仔细想到底什么样的搜索方式是最优的,最快的,其实和那个平方数很像,你枚举数,或者枚举值域是否访问,一样的暴力,但是时间就是千差万别。

T2是构造题,暴力本来可以拿25但是马虎又wa了

T3是数据结构,难就难在转化,二维转化一维的角度没想到

T4期望,暴力因为拓扑没想到也wa了。总之暴力打的很水,再接再厉!

posted on 2022-09-04 16:54  HZOI-曹蓉  阅读(242)  评论(0编辑  收藏  举报