暑假集训CSP提高模拟11

暑假集训CSP提高模拟11

组题人: @KafuuChinocpp

\(T1\) P152. Fate \(24pts\)

  • 强化版: HDU1688 Sightseeing | luogu P2865 [USACO06NOV] Roadblocks G

  • \(dis_{i,0/1}\) 表示从 \(s\)\(i\) 的最短路/次短路长度, \(f_{i,0/1}\) 表示从 \(s\)\(i\) 的最短路/次短路条数。

  • \(dijkstra\) 过程中按照路径长度与最短路、次短路的大小关系四种情况进行反讨。

  • 由于需要统计最短路及次短路,vis 数组同样需要记录当前是最短路还是次短路去更新状态

  • 最后判断一下次短路长度是否等于最短路长度加一。

    点击查看代码
    const ll p=1000000007;
    struct node
    {
    	ll nxt,to,w;
    }e[400010];
    struct quality
    {
    	ll dis,x,id;
    	bool operator < (const quality another) const
    	{
    		return dis>another.dis;
    	}
    };
    ll head[400010],vis[400010][2],dis[400010][2],f[400010][2],cnt=0;
    void add(ll u,ll v,ll w)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	e[cnt].w=w;
    	head[u]=cnt;
    }	
    void dijkstra(ll s)
    {
    	ll x,id,i;
    	priority_queue<quality>q;
    	memset(dis,0x3f,sizeof(dis));
    	memset(vis,0,sizeof(vis));
    	dis[s][0]=0;
    	f[s][0]=1;
    	q.push((quality){dis[s][0],s,0});
    	while(q.empty()==0)
    	{
    		x=q.top().x;
    		id=q.top().id;
    		q.pop();
    		if(vis[x][id]==0)
    		{
    			vis[x][id]=1;
    			for(i=head[x];i!=0;i=e[i].nxt)
    			{
    				if(dis[e[i].to][0]>dis[x][id]+e[i].w)
    				{
    					dis[e[i].to][1]=dis[e[i].to][0];
    					f[e[i].to][1]=f[e[i].to][0];
    					dis[e[i].to][0]=dis[x][id]+e[i].w;
    					f[e[i].to][0]=f[x][id];
    					q.push((quality){dis[e[i].to][0],e[i].to,0});
    					q.push((quality){dis[e[i].to][1],e[i].to,1});
    				}
    				else
    				{
    					if(dis[e[i].to][0]==dis[x][id]+e[i].w)
    					{
    						f[e[i].to][0]=(f[e[i].to][0]+f[x][id])%p;
    					}
    					else
    					{
    						if(dis[e[i].to][1]>dis[x][id]+e[i].w)
    						{
    							dis[e[i].to][1]=dis[x][id]+e[i].w;
    							f[e[i].to][1]=f[x][id];
    							q.push((quality){dis[e[i].to][1],e[i].to,1});
    						}
    						else
    						{
    							if(dis[e[i].to][1]==dis[x][id]+e[i].w)
    							{
    								f[e[i].to][1]=(f[e[i].to][1]+f[x][id])%p;
    							}
    						}
    					}
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	ll n,m,s,t,u,v,i;
    	cin>>n>>m>>s>>t;
    	for(i=1;i<=m;i++)
    	{
    		cin>>u>>v;
    		add(u,v,1);
    		add(v,u,1);
    	}
    	dijkstra(s);
    	cout<<(dis[t][1]-1==dis[t][0])*f[t][1]<<endl;
    	return 0;
    }
    

\(T2\) P153. EVA \(5pts\)

  • 原题: [ABC274F] Fishing

  • 从贪心的角度分析,网的一端和至少一条鱼重合一定不劣。

  • 考虑枚举与左端点重合的鱼,然后固定这条鱼,令其他鱼与其做相对运动,处理出进入网的左右 \(t\) 边界,差分后,由于左端点已经固定,所以直接继承即可。

  • 特判速度相等的情况。

  • 略卡精度。

    点击查看代码
    const double eps=1e-10;
    int w[2010],x[2010],v[2010];
    map<double,int>d;
    map<double,int>::iterator it;
    int main()
    {
    	int n,a,ans=0,sum,i,j;
    	double l,r;
    	cin>>n>>a;
    	for(i=1;i<=n;i++)
    	{
    		cin>>w[i]>>x[i]>>v[i];
    	}
    	for(i=1;i<=n;i++)
    	{
    		d.clear();
    		sum=w[i];
    		for(j=1;j<=n;j++)
    		{
    			if(i!=j)
    			{
    				if(v[i]==v[j])
    				{
    					if(x[i]<=x[j]&&x[j]<=x[i]+a)
    					{
    						sum+=w[j];
    					}
    				}
    				else
    				{
    					l=1.0*(x[i]-x[j])/(v[j]-v[i]);
    					r=1.0*(x[i]+a-x[j])/(v[j]-v[i]);	
    					if(l-r>eps)//左右端点颠倒了要颠倒回来
    					{
    						swap(l,r);
    					}
    					if(r>=0)//保证存在区间
    					{
    						l=max(l,0.0);
    						d[l]+=w[j];
    						d[r+eps]-=w[j];
    					}
    				}
    			}
    		}
    		ans=max(ans,sum);
    		for(it=d.begin();it!=d.end();it++)
    		{
    			sum+=it->second;
    			ans=max(ans,sum);
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

\(T3\) P166. 嘉然登场 \(5pts\)

  • 原题: [ARC148E] ≥ K

  • \(\{ a \}\) 排序后分成 \(< \frac{k}{2}\)\(\ge \frac{k}{2}\) 两部分,然后进行分讨。

  • \(x,y<\frac{n}{2}\) 时, \(x,y\) 不能相邻。

  • \(x<\frac{k}{2} \le y\) 时,若 \(|x-\frac{k}{2}| \le |y-\frac{k}{2}|\) 则可以相邻,否则不能相邻。

  • \(x,y \ge \frac{k}{2}\) 时, \(x,y\) 可以相邻。

  • \(|a_{i}-\frac{k}{2}|\) 进行排序,然后降序插入。

  • \(< \frac{k}{2}\) 的数看作 \(0\) ,否则看作 \(1\) 。插入的过程中要求插入的数必须两边都和 \(1\) 相邻,即只能插到 \(1?1\) 中来(没有位置的话可以拆开硬插)。

  • 设当前可以插入的位置共有 \(s\) 个(初始时 \(s=1\) )。当目前要插入 \(t\)\(0\) 时,由于一个位置仅可以插一个数,对答案产生的贡献为 \(\dbinom{s}{t}\) ,同时可以插入的位置减少 \(t\) 个;当目前要插入 \(t\)\(1\) 时,由于一个位置可以插多个数,对答案产生的贡献为 \(\dbinom{s+t-1}{t}\) ,同时可以插入的位置增加 \(t\) 个。

    点击查看代码
    const ll p=998244353;
    ll jc[200010],inv[200010],jc_inv[200010],a[200010],k;
    map<ll,ll>cnt;
    bool cmp(ll a,ll b)
    {
    	return (abs(2*a-k)==abs(2*b-k))?(a>b):(abs(2*a-k)>abs(2*b-k));
    }
    ll C(ll n,ll m,ll p)
    {
    	return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m]%p)*jc_inv[m]%p:0;
    }
    int main()
    {
    	ll n,m,ans=1,sum=1,i;
    	cin>>n>>k;
    	inv[1]=1;
    	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
    	for(i=2;i<=n;i++)
    	{
    		inv[i]=(p-p/i)*inv[p%i]%p;
    		jc[i]=jc[i-1]*i%p;
    		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
    	}
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		cnt[a[i]]++;
    	}
    	sort(a+1,a+1+n);
    	m=unique(a+1,a+1+n)-(a+1);
    	sort(a+1,a+1+m,cmp);
    	for(i=1;i<=m;i++)
    	{
    		if(2*a[i]<k)
    		{
    			ans=ans*C(sum,cnt[a[i]],p)%p;
    			sum-=cnt[a[i]];
    		}
    		else
    		{
    			ans=ans*C(sum+cnt[a[i]]-1,cnt[a[i]],p)%p;
    			sum+=cnt[a[i]];
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

\(T4\) P178. Clannad \(0pts\)

  • 原题: luogu P9340 [JOISC 2023 Day3] Tourism

  • 大力结论

    • \(a_{l \sim r}\)\(DFS\) 序排序后,最小连通块(虚树)边数为 \(\dfrac{dis_{a_{l},a_{l+1}}+dis_{a_{l+1},a_{l+2}}+ \dots +dis_{a_{r-1},a_{r}}+dis_{a_{r},a_{l}}}{2}\) ,点数等于边数加一。
  • 普通莫队套 set 维护 \(DFS\) 序的前驱后继的话时间复杂度为 \(O(n\sqrt{n} \log n)\) ,瓶颈在 set 的动态插入和动态删除。

  • 考虑优化插入和删除过程,我们采用只减不加回滚莫队,右端点设为 \(m\) ,这样的话链表就可以支持 \(O(1)\) 删除了。

  • \(LCA\) 用树剖的话时间复杂度为 \(O(n \sqrt{n} \log n)\) ,加个超级快读在 luogu 贴着线过,但在学校 \(OJ\) 上过不了;使用 \(DFS\) 序求 \(LCA\) ,略带卡常,需要交换 \(ST\) 表两维下标和 __lg

    点击查看代码
    namespace IO{
    	#ifdef LOCAL
    	FILE*Fin(fopen("test.in","r")),*Fout(fopen("test.out","w"));
    	#else
    	FILE*Fin(stdin),*Fout(stdout);
    	#endif
    	class qistream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qistream(FILE*_fp=stdin):fp(_fp),p(0){fread(buf+p,1,SIZE-p,fp);}void flush(){memmove(buf,buf+p,SIZE-p),fread(buf+SIZE-p,1,p,fp),p=0;}qistream&operator>>(char&x){x=getch();while(isspace(x))x=getch();return*this;}template<class T>qistream&operator>>(T&x){x=0;p+BLOCK>=SIZE?flush():void();bool flag=false;for(;!isdigit(buf[p]);++p)flag=buf[p]=='-';for(;isdigit(buf[p]);++p)x=x*10+buf[p]-'0';x=flag?-x:x;return*this;}char getch(){p+BLOCK>=SIZE?flush():void();return buf[p++];}qistream&operator>>(char*str){char ch=getch();while(ch<=' ')ch=getch();int i=0;for(;ch>' ';++i,ch=getch())str[i]=ch;str[i]='\0';return*this;}}qcin(Fin);
    	class qostream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qostream(FILE*_fp=stdout):fp(_fp),p(0){}~qostream(){fwrite(buf,1,p,fp);}void flush(){fwrite(buf,1,p,fp),p=0;}template<class T>qostream&operator<<(T x){int len=0;p+BLOCK>=SIZE?flush():void();x<0?(x=-x,buf[p++]='-'):0;do buf[p+len]=x%10+'0',x/=10,++len;while(x);for(int i=0,j=len-1;i<j;++i,--j)std::swap(buf[p+i],buf[p+j]);p+=len;return*this;}qostream&operator<<(char x){putch(x);return*this;}void putch(char ch){p+BLOCK>=SIZE?flush():void();buf[p++]=ch;}qostream&operator<<(char*str){for(int i=0;str[i];++i)putch(str[i]);return*this;}qostream&operator<<(const char*s){for(int i=0;s[i];++i)putch(s[i]);return*this;}}qcout(Fout);
    }
    #define cin IO::qcin
    #define cout IO::qcout
    struct quality
    {
    	int pre,nxt,pos;
    }x;
    vector<quality>s;
    struct node
    {
    	int nxt,to;
    }e[200010];
    int head[200010],dep[200010],rk[200010],dfn[200010],a[200010],pos[200010],L[200010],R[200010],ans[200010],num[200010],pre[200010],nxt[2000010],tot=0,cnt=0,klen,ksum;
    struct ask
    {
    	int l,r,id;
    }q[200010];
    bool q_cmp(ask a,ask b)
    {
    	return (pos[a.l]==pos[b.l])?(a.r>b.r):(a.l<b.l);
    }
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    int sx_min(int x,int y)
    {
    	return dfn[x]<dfn[y]?x:y;
    }
    struct ST
    {
    	int fminn[25][200010];
    	void init(int n)
    	{
    		for(int j=1;j<=__lg(n);j++)
    		{
    			for(int i=1;i+(1<<j)-1<=n;i++)
    			{
    				fminn[j][i]=sx_min(fminn[j-1][i],fminn[j-1][i+(1<<(j-1))]);	
    			}
    		}
    	}
    	int query(int l,int r)
    	{
    		int t=__lg(r-l+1);
    		return sx_min(fminn[t][l],fminn[t][r-(1<<t)+1]);
    	}
    }T;
    void dfs(int x,int fa)
    {
    	tot++;
    	dfn[x]=tot;
    	T.fminn[0][tot]=fa;
    	rk[tot]=x;
    	dep[x]=dep[fa]+1;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			dfs(e[i].to,x);
    		}
    	}
    }
    int lca(int u,int v)
    {
    	if(dfn[u]>dfn[v])
    	{	
    		swap(u,v);
    	}
    	return (dfn[u]==dfn[v])?u:T.query(dfn[u]+1,dfn[v]);
    }
    int dis(int x,int y)
    {
    	x=rk[x];
    	y=rk[y];
    	return dep[x]+dep[y]-2*dep[lca(x,y)];
    }
    void init(int n,int m)
    {
    	klen=n/sqrt(m)+1;
    	ksum=n/klen;
    	for(int i=1;i<=ksum;i++)
    	{
    		L[i]=R[i-1]+1;
    		R[i]=R[i-1]+klen;
    	}
    	if(R[ksum]<n)
    	{
    		ksum++;
    		L[ksum]=R[ksum-1]+1;
    		R[ksum]=n;
    	}
    	for(int i=1;i<=ksum;i++)
    	{
    		for(int j=L[i];j<=R[i];j++)
    		{
    			pos[j]=i;
    		}
    	}
    }
    void del1(int x,int &sum)
    {
    	num[x]--;
    	if(num[x]==0)
    	{
    		sum-=dis(pre[x],x);
    		sum-=dis(x,nxt[x]);
    		sum+=dis(pre[x],nxt[x]);
    		pre[nxt[x]]=pre[x];
    		nxt[pre[x]]=nxt[x];
    	}
    }
    void del2(int x,int &sum)
    {
    	num[x]--;
    	s.push_back((quality){pre[x],nxt[x],x});//后面要撤销这里的影响
    	if(num[x]==0)
    	{
    		sum-=dis(pre[x],x);
    		sum-=dis(x,nxt[x]);
    		sum+=dis(pre[x],nxt[x]);
    		pre[nxt[x]]=pre[x];
    		nxt[pre[x]]=nxt[x];
    	}
    }
    int main()
    {
    	int n,m,k,u,v,sum=0,tmp,l=1,r,last,i,j;
    	cin>>n>>m>>k;
    	for(i=1;i<=n-1;i++)
    	{
    		cin>>u>>v;
    		add(u,v);
    		add(v,u);
    	}
    	dfs(1,0);
    	T.init(n);
    	init(m,k);
    	for(i=1;i<=m;i++)
    	{
    		cin>>a[i];
    	}
    	for(i=1;i<=k;i++)
    	{
    		cin>>q[i].l>>q[i].r;
    		q[i].id=i;
    	}
    	sort(q+1,q+1+k,q_cmp);
    	for(i=1;i<=k;i++)
    	{
    		if(pos[q[i].l]!=pos[q[i-1].l])
    		{
    			memset(num,0,sizeof(num));
    			for(j=L[pos[q[i].l]];j<=m;j++)
    			{
    				num[dfn[a[j]]]++;
    			}
    			last=0;
    			for(j=1;j<=n;j++)
    			{
    				pre[j]=last;
    				last=(num[j]==0)?last:j;
    			}
    			for(j=1;j<=n;j++)
    			{
    				pre[j]=(pre[j]==0)?last:pre[j];//因为只有首尾的 num 不等于 0 ,所以全赋值也没什么大碍
    			}
    			last=0;
    			for(j=n;j>=1;j--)
    			{
    				nxt[j]=last;
    				last=(num[j]==0)?last:j;
    			}
    			for(j=n;j>=1;j--)
    			{
    				nxt[j]=(nxt[j]==0)?last:nxt[j];
    			}
    			sum=0;
    			for(j=1;j<=n;j++)
    			{
    				sum+=(num[j]!=0)*dis(pre[j],j);
    			}
    			r=m;
    		}
    		l=L[pos[q[i].l]];
    		while(r>q[i].r)
    		{
    			del1(dfn[a[r]],sum);
    			r--;
    		}
    		tmp=sum;
    		while(l<q[i].l)
    		{
    			del2(dfn[a[l]],sum);
    			l++;
    		}
    		ans[q[i].id]=sum/2+1;
    		sum=tmp;
    		while(s.empty()==0)//撤销回滚的影响
    		{
    			x=s.back();
    			s.pop_back();
    			if(num[x.pos]==0)
    			{
    				nxt[x.pre]=x.pos;
    				pre[x.nxt]=x.pos;
    			}
    			num[x.pos]++;
    		}
    	}
    	for(i=1;i<=k;i++)
    	{
    		cout<<ans[i]<<endl;
    	}
    	return 0;
    }
    
  • 树剖加珂朵莉树的做法会好写点,同 luogu P8512 [Ynoi Easy Round 2021] TEST_152 扫描线维护时间戳对答案的贡献。

    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[200010];
    int head[200010],fa[200010],siz[200010],dep[200010],son[200010],top[200010],dfn[200010],ans[200010],a[200010],tot=0,cnt=0;
    vector<pair<int,int> >q[200010];
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dfs1(int x,int father)
    {
    	siz[x]=1;
    	fa[x]=father;
    	dep[x]=dep[father]+1;
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=father)
    		{
    			dfs1(e[i].to,x);
    			siz[x]+=siz[e[i].to];
    			son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x];
    		}
    	}
    }
    void dfs2(int x,int id)
    {
    	top[x]=id;
    	tot++;
    	dfn[x]=tot;
    	if(son[x]!=0)
    	{
    		dfs2(son[x],id);
    		for(int i=head[x];i!=0;i=e[i].nxt)
    		{
    			if(e[i].to!=fa[x]&&e[i].to!=son[x])
    			{
    				dfs2(e[i].to,e[i].to);
    			}
    		}
    	}
    }
    struct BIT
    {
    	int c[200010];
    	int lowbit(int x)
    	{
    		return (x&(-x));
    	}
    	void add(int n,int x,int val)
    	{
    		if(x==0)
    		{
    			return;
    		}
    		for(int i=x;i<=n;i+=lowbit(i))
    		{
    			c[i]+=val;
    		}
    	}
    	int getsum(int x)
    	{
    		int ans=0;
    		for(int i=x;i>=1;i-=lowbit(i))
    		{
    			ans+=c[i];
    		}
    		return ans;
    	}
    }T;
    struct ODT
    {
    	struct node
    	{
    		int l,r;
    		mutable int col;
    		bool operator < (const node &another) const
    		{
    			return l<another.l;
    		}
    	};
    	set<node>s;
    	void init(int n)
    	{
    		s.insert((node){1,n,0});
    	}
    	set<node>::iterator split(int pos)
    	{
    		set<node>::iterator it=s.lower_bound((node){pos,0,0});
    		if(it!=s.end()&&it->l==pos)
    		{
    			return it;
    		}
    		it--;
    		if(it->r<pos)
    		{
    			return s.end();
    		}
    		int l=it->l,r=it->r,col=it->col;
    		s.erase(it);
    		s.insert((node){l,pos-1,col});
    		return s.insert((node){pos,r,col}).first;
    	}
    	void assign(int l,int r,int col,int n)
    	{
    		set<node>::iterator itr=split(r+1),itl=split(l);
    		for(set<node>::iterator it=itl;it!=itr;it++)
    		{
    			T.add(n,it->col,-(it->r-it->l+1));
    		}
    		T.add(n,col,r-l+1);
    		s.erase(itl,itr);
    		s.insert((node){l,r,col});
    	}
    }O;
    void update(int u,int v,int col,int n)
    {
    	while(top[u]!=top[v])
    	{
    		if(dep[top[u]]>dep[top[v]])
    		{
    			O.assign(dfn[top[u]],dfn[u],col,n);
    			u=fa[top[u]];
    		}
    		else
    		{
    			O.assign(dfn[top[v]],dfn[v],col,n);
    			v=fa[top[v]];
    		}
    	}
    	if(dep[u]<dep[v])
    	{
    		O.assign(dfn[u],dfn[v],col,n);
    	}
    	else
    	{
    		O.assign(dfn[v],dfn[u],col,n);
    	}
    }
    int main()
    {
    // #define Isaac
    #ifdef Isaac
    	freopen("in.in","r",stdin);
    	freopen("out.out","w",stdout);
    #endif
    	int n,m,k,u,v,i,j;
    	cin>>n>>m>>k;
    	for(i=1;i<=n-1;i++)
    	{
    		cin>>u>>v;
    		add(u,v);
    		add(v,u);
    	}
    	for(i=1;i<=m;i++)
    	{
    		cin>>a[i];
    	}
    	for(i=1;i<=k;i++)
    	{
    		cin>>u>>v;
    		if(u==v)
    		{
    			ans[i]=1;
    		}
    		else
    		{
    			q[v].push_back(make_pair(u,i));
    		}
    	}
    	dfs1(1,0);
    	dfs2(1,1);
    	O.init(n);
    	for(i=2;i<=m;i++)
    	{
    		update(a[i-1],a[i],i,m);
    		for(j=0;j<q[i].size();j++)
    		{
    			ans[q[i][j].second]=T.getsum(i)-T.getsum(q[i][j].first);
    		}
    	}
    	for(i=1;i<=k;i++)
    	{
    		cout<<ans[i]<<endl;
    	}
    	return 0;
    }
    

总结

  • \(T1\)
    • 没想到在 \(dijkstra\) 过程中同样也能更新次短路,说明对 \(dijkstra\) 节点标记的认识不清楚。
    • 在最短路 \(DAG\) 上统计答案上仅考虑到了最短路径上的点,没考虑到非最短路径上的点也能转移过来。
  • \(T2\)
    • 被所选择的 \(x,t\) 可以不为整数给吓到了,没敢再想贪心。

后记

posted @ 2024-07-29 18:52  hzoi_Shadow  阅读(256)  评论(19编辑  收藏  举报
扩大
缩小