CSP-S模拟2(联考) 谜之阶乘 子集 混凝土粉末 排水系统
rank 40 40多分?
T1:暴力;T2:数学构造;T3:数据结构;T4:概率期望
T1
T2:构造出(1--n)的连续整数分成k组,每组的数加起来一样。(n<=1e6)
只要能实现一种构造方案,使得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;
}
/*
*/