暑假集训CSP提高模拟 25

暑假集训CSP提高模拟 25

组题人: @KafuuChinocpp | @H_Kaguya

\(T1\) P235.可持久化线段树 \(0pts\)

  • 强化版: luogu U259681 败者食尘(加强版)
  • 做法有点多,记一下。
    • 标记永久化主席树板子。时空复杂度为 \(O(m \log n)\)

      点击查看代码
      const ll p=998244353;
      ll a[100010];
      struct PDS_SMT
      {
      	ll root[100010],rt_sum;
      	struct SegmentTree
      	{
      		ll ls,rs,sum,lazy;
      	}tree[100010<<5];
      	#define lson(rt) tree[rt].ls
      	#define rson(rt) tree[rt].rs
      	ll build_rt()
      	{
      		rt_sum++;
      		return rt_sum;
      	}
      	void build_tree(ll &rt,ll l,ll r)
      	{
      		rt=build_rt();
      		if(l==r)
      		{
      			tree[rt].sum=a[l];
      			return;
      		}
      		ll mid=(l+r)/2;
      		build_tree(lson(rt),l,mid);
      		build_tree(rson(rt),mid+1,r);
      		tree[rt].sum=(tree[lson(rt)].sum+tree[rson(rt)].sum)%p;
      	}
      	void update(ll pre,ll &rt,ll l,ll r,ll x,ll y,ll val)
      	{
      		rt=build_rt();
      		tree[rt]=tree[pre];
      		tree[rt].sum=(tree[rt].sum+(min(r,y)-max(l,x)+1)*val%p)%p;
      		if(x<=l&&r<=y)
      		{
      			tree[rt].lazy=(tree[rt].lazy+val)%p;
      			return;
      		}
      		ll mid=(l+r)/2;
      		if(x<=mid)
      		{
      			update(lson(pre),lson(rt),l,mid,x,y,val);
      		}
      		if(y>mid)
      		{
      			update(rson(pre),rson(rt),mid+1,r,x,y,val);
      		}
      	}
      	ll query(ll rt,ll l,ll r,ll x,ll y)
      	{
      		if(x<=l&&r<=y)
      		{
      			return tree[rt].sum;
      		}
      		ll mid=(l+r)/2,ans=0;
      		if(x<=mid)
      		{
      			ans=(ans+query(lson(rt),l,mid,x,y))%p;
      		}
      		if(y>mid)
      		{
      			ans=(ans+query(rson(rt),mid+1,r,x,y))%p;
      		}
      		return (ans+tree[rt].lazy*(min(r,y)-max(l,x)+1)%p)%p;
      	}
      }T;
      int main()
      {
      	ll n,m,pd,l,r,x,c_cnt=0,i;
      	cin>>n>>m;
      	for(i=1;i<=n;i++)
      	{
      		cin>>a[i];
      	}
      	T.build_tree(T.root[0],1,n);
      	for(i=1;i<=m;i++)
      	{
      		cin>>pd;
      		if(pd==1)
      		{
      			cin>>l>>r>>x;
      			c_cnt++;
      			T.update(T.root[c_cnt-1],T.root[c_cnt],1,n,l,r,x);
      		}
      		if(pd==2)
      		{
      			cin>>l>>r;
      			cout<<T.query(T.root[c_cnt],1,n,l,r)<<endl;
      		}
      		if(pd==3)
      		{
      			cin>>x;
      			c_cnt-=x;
      		}
      	}
      	return 0;
      }
      
    • 撤销对 \([l,r]\) 的区间加 \(x\) 等价于让 \([l,r]\) 区间加 \(-x\) ,所以暴力撤销即可,随便套个什么支持区间修改、区间求和的数据结构即可。树状数组、线段树的时间复杂度为 \(O(m \log n)\) ,分块的时间复杂度为 \(O(m\sqrt{n})\) ,空间发展度均为 \(O(n)\)

      • 为过加强版加了个快读不介意吧。

        点击查看代码
        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
        const ll p=998244353;
        ll a[500010];
        pair<ll,pair<ll,ll> >c[500010];
        struct BIT
        {
        	ll c[2][500010];
        	ll lowbit(ll x)
        	{
        		return (x&(-x));
        	}
        	void init(ll n,ll a[])
        	{
        		for(ll i=1;i<=n;i++)
        		{
        			add(n,i,(a[i]-a[i-1]+p)%p);
        		}
        	}
        	void add(ll n,ll x,ll val)
        	{
        		for(ll i=x;i<=n;i+=lowbit(i))
        		{
        			c[0][i]=(c[0][i]+val)%p;
        			c[1][i]=(c[1][i]+val*x%p)%p;
        		}
        	}
        	ll getsum(ll x,ll c[])
        	{
        		ll ans=0;
        		for(ll i=x;i>=1;i-=lowbit(i))
        		{
        			ans=(ans+c[i])%p;
        		}
        		return ans;
        	}
        	void update(ll n,ll l,ll r,ll val)
        	{
        		add(n,l,(val+p)%p);
        		add(n,r+1,(-val+p)%p);
        	}
        	ll query(ll l,ll r)
        	{
        		return (getsum(r,c[0])*(r+1)%p-getsum(l-1,c[0])*l%p-getsum(r,c[1])+getsum(l-1,c[1])+2*p)%p;
        	}
        }T;
        int main()
        {
        	ll n,m,pd,l,r,x,c_cnt=0,i,j;
        	cin>>n>>m;
        	for(i=1;i<=n;i++)
        	{
        		cin>>a[i];
        	}
        	T.init(n,a);
        	for(i=1;i<=m;i++)
        	{
        		cin>>pd;
        		if(pd==1)
        		{
        			cin>>l>>r>>x;
        			c_cnt++;
        			c[c_cnt]=make_pair(l,make_pair(r,x));
        			T.update(n,l,r,x);
        		}
        		if(pd==2)
        		{
        			cin>>l>>r;
        			cout<<T.query(l,r)<<endl;
        		}
        		if(pd==3)
        		{
        			cin>>x;
        			for(j=1;j<=x;j++)			
        			{
        				T.update(n,c[c_cnt].first,c[c_cnt].second.first,-c[c_cnt].second.second);
        				c_cnt--;
        			}
        		}
        	}
        	return 0;
        }
        
    • 可撤销线段树

    • 线段树分治

\(T2\) P208.Little Busters ! \(0pts\)

  • 原题: 2024牛客暑期多校训练营6 D Puzzle: Wagiri

  • Lun 边至少属于图中的一个环等价于 Lun 边连接的两个端点属于一个边双连通分量,Qie 边不属于图中的任意环等价于 Qie 边连接的两个端点不属于一个边双连通分量。

  • 部分分

    • \(20 \%\) :枚举每条边保留或不保留,然后 \(Tarjan\) 判断是否合法。
    • 另外 \(20 \%\) :缩完点后只能有一个点否则不满足题意。
    • 另外 \(20 \%\) :直接删成一棵树就行了。
    • 随机 \(pts\) :乱搞。
    点击查看代码
    struct node
    {
    	int nxt,to,w,id;
    }E[200010];
    int head[200010],u[200010],v[200010],w[200010],dfn[200010],low[200010],c[200010],vis[200010],tot=0,cnt=0,scc_cnt=0;
    vector<pair<int,int> >e[200010],ans;
    stack<int>s;
    void add(int u,int v,int w,int id)
    {
    	cnt++;
    	E[cnt].nxt=head[u];
    	E[cnt].to=v;
    	E[cnt].w=w;
    	E[cnt].id=id;
    	head[u]=cnt;
    }
    void tarjan(int x,int fa)
    {
    	tot++;
    	dfn[x]=low[x]=tot;
    	s.push(x);
    	for(int i=0;i<e[x].size();i++)
    	{
    		if(e[x][i].first!=fa)
    		{
    			if(dfn[e[x][i].first]==0)
    			{
    				tarjan(e[x][i].first,x);
    				low[x]=min(low[x],low[e[x][i].first]);
    			}
    			else
    			{
    				low[x]=min(low[x],dfn[e[x][i].first]);
    			}
    		}
    	}
    	if(dfn[x]==low[x])
    	{
    		int k;
    		scc_cnt++;
    		do
    		{
    			k=s.top();
    			s.pop();
    			c[k]=scc_cnt;
    		}while(k!=x);
    	}
    }
    void tarjanE(int x,int fa)
    {
    	tot++;
    	dfn[x]=low[x]=tot;
    	s.push(x);
    	for(int i=head[x];i!=0;i=E[i].nxt)
    	{
    		if(vis[E[i].id]==0&&E[i].to!=fa)
    		{
    			if(dfn[E[i].to]==0)
    			{
    				tarjanE(E[i].to,x);
    				low[x]=min(low[x],low[E[i].to]);
    			}
    			else
    			{
    				low[x]=min(low[x],dfn[E[i].to]);
    			}
    		}
    	}
    	if(dfn[x]==low[x])
    	{
    		int k;
    		scc_cnt++;
    		do
    		{
    			k=s.top();
    			s.pop();
    			c[k]=scc_cnt;
    		}while(k!=x);
    	}
    }
    bool check(int state,int n,int m)
    {
    	if(__builtin_popcount(state)<n-1)
    	{
    		return false;
    	}
    	scc_cnt=tot=0;
    	ans.clear();
    	for(int i=1;i<=n;i++)
    	{
    		dfn[i]=low[i]=c[i]=0;
    		e[i].clear();
    	}
    	while(s.empty()==0)
    	{
    		s.pop();
    	}
    	for(int i=0;i<=m-1;i++)
    	{
    		if((state>>i)&1)
    		{
    			e[u[i+1]].push_back(make_pair(v[i+1],w[i+1]));
    			e[v[i+1]].push_back(make_pair(u[i+1],w[i+1]));
    			ans.push_back(make_pair(u[i+1],v[i+1]));
    		}
    	}
    	tarjan(1,0);
    	for(int i=1;i<=n;i++)
    	{
    		if(dfn[i]==0)
    		{
    			return false;
    		}
    	}
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=0;j<e[i].size();j++)
    		{
    			if(e[i][j].second==1)
    			{
    				if(c[i]!=c[e[i][j].first])
    				{
    					return false;
    				}
    			}
    			else
    			{
    				if(c[i]==c[e[i][j].first])
    				{
    					return false;
    				}
    			}
    		}
    	}
    	return true;
    }
    void dfs(int x,int fa)
    {
    	vis[x]=1;
    	for(int i=0;i<e[x].size();i++)
    	{
    		if(vis[e[x][i].first]==0)
    		{
    			cout<<x<<" "<<e[x][i].first<<endl;
    			dfs(e[x][i].first,x);
    		}
    	}
    }	
    int main()
    {
    	int n,m,flag1=1,flag2=1,state,i,j;
    	string pd;
    	cin>>n>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>u[i]>>v[i]>>pd;
    		w[i]=(pd=="Lun");
    		flag1&=(w[i]==1);
    		flag2&=(w[i]==0);
    	}
    	if(flag1==1)
    	{
    		for(i=1;i<=m;i++)
    		{
    			e[u[i]].push_back(make_pair(v[i],w[i]));
    			e[v[i]].push_back(make_pair(u[i],w[i]));
    		}
    		tarjan(1,0);
    		if(scc_cnt==1)
    		{
    			cout<<"YES"<<endl;
    			cout<<m<<endl;
    			for(i=1;i<=m;i++)
    			{
    				cout<<u[i]<<" "<<v[i]<<endl;
    			}
    		}
    		else
    		{
    			cout<<"NO"<<endl;
    		}
    	}
    	else
    	{
    		if(flag2==1)
    		{
    			for(i=1;i<=m;i++)
    			{
    				e[u[i]].push_back(make_pair(v[i],w[i]));
    				e[v[i]].push_back(make_pair(u[i],w[i]));
    			}
    			cout<<"YES"<<endl;
    			cout<<n-1<<endl;
    			dfs(1,0);
    		}
    		else
    		{
    			if(m<=20)
    			{
    				for(state=0;state<=(1<<m)-1;state++)
    				{
    					if(check(state,n,m)==true)
    					{
    						cout<<"YES"<<endl;
    						cout<<ans.size()<<endl;
    						for(i=0;i<ans.size();i++)
    						{
    							cout<<ans[i].first<<" "<<ans[i].second<<endl;
    						}
    						return 0;
    					}
    				}
    				cout<<"NO"<<endl;
    			}
    			else
    			{
    				for(i=1;i<=m;i++)
    				{
    					add(u[i],v[i],w[i],i);
    					add(v[i],u[i],w[i],i);
    					e[u[i]].push_back(make_pair(v[i],w[i]));
    					e[v[i]].push_back(make_pair(u[i],w[i]));
    				}
    				tarjan(1,0);
    				for(int i=1;i<=n;i++)
    				{
    					for(int j=head[i];j!=0;j=E[j].nxt)
    					{
    						if(E[j].w==1)
    						{
    							if(c[j]!=c[E[j].to])
    							{
    								vis[E[j].id]=1;
    							}
    						}
    						else
    						{
    							if(c[j]!=c[E[j].to])
    							{
    								vis[E[j].id]=0;
    							}
    						}
    					}
    				}
    				scc_cnt=tot=0;
    				ans.clear();
    				for(int i=1;i<=n;i++)
    				{
    					dfn[i]=low[i]=c[i]=0;
    					e[i].clear();
    				}
    				while(s.empty()==0)
    				{
    					s.pop();
    				}
    				tarjanE(1,0);
    				for(int i=1;i<=n;i++)
    				{
    					if(dfn[i]==0)
    					{
    						cout<<"NO"<<endl;
    						return 0;
    					}
    				}
    				for(int i=1;i<=n;i++)
    				{
    					for(int j=0;j<e[i].size();j++)
    					{
    						if(e[i][j].second==1)
    						{
    							if(c[i]!=c[e[i][j].first])
    							{
    								cout<<"NO"<<endl;
    								return 0;
    							}
    						}
    						else
    						{
    							if(c[i]==c[e[i][j].first])
    							{
    								cout<<"NO"<<endl;
    								return 0;
    							}
    						}
    					}
    				}
    				for(i=1;i<=m;i++)
    				{
    					if(vis[i]==0)
    					{
    						ans.push_back(make_pair(u[i],v[i]));
    					}
    				}
    				cout<<"YES"<<endl;
    				cout<<ans.size()<<endl;
    				for(i=0;i<ans.size();i++)
    				{
    					cout<<ans[i].first<<" "<<ans[i].second<<endl;
    				}
    			}
    		}
    	}
    	return 0;
    }
    
  • 正解

    • 因为 Lun 边至少属于图中的一个边双连通分量,考虑只保留 Lun 边跑一遍 \(Tarjan\) ,接着只保留在边双连通分量上的边,其他边全部删除(加入 Qie 边后若 Lun 边合法则 Qie 边合法)。
    • 接着将边双连通分量进行连边,将 Qie 边作为割边连接两个边双连通分量,并查集维护连通情况即可。
    • 计算合并次数来判断是否有解。
    点击查看代码
    struct node
    {
    	int nxt,to;
    }e[400010];
    int head[200010],dfn[200010],low[200010],ins[200010],belong[200010],tot=0,scc_cnt=0,cnt=0;
    vector<pair<int,int> >ans,lun,qie;
    stack<int>s;
    void add(int u,int v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void tarjan(int x,int fa)
    {
    	tot++;
    	dfn[x]=low[x]=tot;
    	ins[x]=1;
    	s.push(x);
    	for(int i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			if(dfn[e[i].to]==0)
    			{
    				tarjan(e[i].to,x);
    				low[x]=min(low[x],low[e[i].to]);
    			}
    			else
    			{
    				if(ins[e[i].to]==1)
    				{
    					low[x]=min(low[x],dfn[e[i].to]);
    				}
    			}
    		}
    	}
    	if(dfn[x]==low[x])
    	{
    		int k=0;
    		scc_cnt++;
    		while(x!=k)		
    		{
    			k=s.top();
    			s.pop();
    			ins[k]=0;
    			belong[k]=scc_cnt;
    		}
    	}
    }
    struct DSU
    {
    	int fa[200010];
    	void init(int n)
    	{
    		for(int i=1;i<=n;i++)
    		{
    			fa[i]=i;
    		}
    	}
    	int find(int x)
    	{
    		return fa[x]==x?x:fa[x]=find(fa[x]);
    	}
    	void merge(int x,int y,int id)
    	{
    		x=find(x);
    		y=find(y);
    		if(x!=y)
    		{
    			ans.push_back(qie[id]);
    			fa[y]=x;
    			scc_cnt--;
    		}
    	}
    }D;
    int main()
    {
    	int n,m,u,v,i;
    	string pd; 
    	cin>>n>>m;
    	for(i=1;i<=m;i++)
    	{
    		cin>>u>>v>>pd;
    		if(pd=="Lun")
    		{
    			add(u,v);
    			add(v,u);
    			lun.push_back(make_pair(u,v));
    		}
    		else
    		{
    			qie.push_back(make_pair(u,v));
    		}
    	}
    	for(i=1;i<=n;i++)
    	{
    		if(dfn[i]==0)
    		{
    			tarjan(i,0);
    		}
    	}
    	for(i=0;i<lun.size();i++)
    	{
    		if(belong[lun[i].first]==belong[lun[i].second])
    		{
    			ans.push_back(lun[i]);
    		}
    	}
    	D.init(n);
    	for(i=0;i<qie.size();i++)
    	{
    		if(belong[qie[i].first]!=belong[qie[i].second])
    		{
    			D.merge(belong[qie[i].first],belong[qie[i].second],i);
    		}	
    	}
    	if(scc_cnt==1)
    	{
    		cout<<"YES"<<endl;
    		cout<<ans.size()<<endl;
    		for(i=0;i<ans.size();i++)
    		{
    			cout<<ans[i].first<<" "<<ans[i].second<<endl;
    		}
    	}
    	else
    	{
    		cout<<"NO"<<endl;
    	}
    	return 0;
    }
    

\(T3\) P220.魔卡少女樱 \(45pts\)

  • 原题: [ABC276G] Count Sequences

  • 部分分

    • \(65pts\) :设 \(f_{i,j}\) 表示当前处理到 \(i\) 时,且 \(a_{i}=j\) 的合法方案数,状态转移方程为 \(f_{i,j}=\sum\limits_{k=0}^{j-1}[k \not \equiv j \pmod{3}] \times f_{i-1,k}=\sum\limits_{k=0}^{j-1}f_{i-1,k}-\sum\limits_{k=0}^{j-1}[k \equiv j \pmod{3}] \times f_{i-1,k}\) ,边界为 \(f_{1,i}=[i+n-1 \le m]\) 。前缀和优化后时间复杂度为 \(O(nm)\)

      点击查看代码
      const ll p=998244353;
      int f[2][10000010],sum[5];
      int main()
      {
      	int n,m,ans=0,i,j;
      	cin>>n>>m;
      	if(n-1<=m)
      	{
      		for(i=0;i<=m;i++)
      		{
      			f[1][i]=(i+n-1<=m);
      		}
      		for(i=2;i<=n;i++)
      		{
      			sum[0]=sum[1]=sum[2]=sum[3]=0;
      			for(j=0;j<=m;j++)
      			{
      				f[i&1][j]=(sum[3]-sum[j%3]+p)%p;
      				sum[j%3]=(sum[j%3]+f[(i-1)&1][j])%p;
      				sum[3]=(sum[3]+f[(i-1)&1][j])%p;
      			}
      		}
      		for(i=n-1;i<=m;i++)
      		{
      			ans=(ans+f[n&1][i])%p;
      		}
      		cout<<ans<<endl;
      	}
      	else
      	{
      		cout<<0<<endl;
      	}
      	return 0;
      }
      
  • 正解

    • \(a_{0}=0\) ,接着得到差分序列 \(b_{i}=a_{i}-a_{i-1}(i \in [1,n])\) 。由 \(a_{i}>a_{i-1}\) 不妨设 \(b_{i}=3k_{i}+r_{i}\)\(b_{i} \equiv r_{i} \pmod{3}\) ,其中 \(\begin{cases} r_{i} \in \{ 0,1,2 \} & i=1 \\ r_{i} \in \{ 1,2 \} & i \in [2,n] \end{cases}\) 。不难得到 \(\sum\limits_{i=1}^{n}b_{i} \le m\)
    • 考虑统计差分序列 \(\{ b \}\) 的个数来得到 \(\{ a \}\) 的个数。
    • \([2,n]\) 中共有 \(cnt\)\(r\) 等于 \(2\) ,则 \(\sum\limits_{i=1}^{n}r_{i}=n-1+cnt+r_{1} \le m\) ,方案数为 \(\dbinom{n-1}{cnt}\) 。然后再统计 \(k\) 对答案产生的贡献,由 \(\sum\limits_{i=1}^{n}3k_{i} \le m-(n-1+cnt+r_{1})\) 得到 \(\sum\limits_{i=1}^{n}k_{i} \le \left\lfloor \frac{m-(n-1+cnt+r_{1})}{3} \right\rfloor\) ,其方案数为 \(\sum\limits_{j=0}^{\left\lfloor \frac{m-(n-1+cnt+r_{1})}{3} \right\rfloor} \dbinom{n+j-1}{n-1}\)
    • 最终有 \(\sum\limits_{r_{1}=0}^{2}\sum\limits_{cnt=0}^{n-1}\dbinom{n-1}{cnt}\sum\limits_{j=0}^{\left\lfloor \frac{m-(n-1+cnt+r_{1})}{3} \right\rfloor} \dbinom{n+j-1}{n-1}\) 即为所求。前缀和维护最后面的部分即可。
    点击查看代码
    const ll p=998244353;
    ll jc[20000010],inv[10000010],jc_inv[10000010],f[10000010];
    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=0,i,j;
    	cin>>n>>m;
    	inv[1]=1;
    	jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
    	for(i=2;i<=max(n,m);i++)
    	{
    		inv[i]=(p-p/i)*inv[p%i]%p;
    		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
    	}
    	for(i=2;i<=n+m;i++)
    	{
    		jc[i]=jc[i-1]*i%p;
    	}
    	f[0]=C(n-1,n-1,p);
    	for(i=1;i<=m;i++)
    	{
    		f[i]=(f[i-1]+C(n+i-1,n-1,p))%p;
    	}
    	for(i=0;i<=2;i++)
    	{
    		for(j=0;j<=n-1&&n-1+i+j<=m;j++)
    		{
    			ans=(ans+C(n-1,j,p)*f[(m-(n-1+i+j))/3]%p)%p;
    		}
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

\(T4\) P202.声之形 \(5pts\)

  • 原题: 2024牛客暑期多校训练营5 K 思
  • 感觉想一个二分查找的过程但不会处理上下取整的问题,挂一下官方题解。

    对于 \(10\%\) 的数据:取 \(y=\left\lfloor \frac{a_{1}+a_{2}}{2} \right\rfloor\) ,代价即为 \(a_{2}-\left\lfloor \frac{a_{1}+a_{2}}{2} \right\rfloor\) 或取 \(y=\left\lceil \frac{a_{1}+a_{2}}{2} \right\rceil\) ,代价即为 \(\left\lceil \frac{a_{1}+a_{2}}{2} \right\rceil-a_{1}\)

    对于另外 \(10\%\) 的数据:直接二分。

    对于 \(40\%\) 的数据:

    dp 的想法是去绝对值。

    \(f_{L, R, c}\) 表示当前 \(x\) 在区间 \([L, R]\) 内,位于区间左侧的询问数量减去位于区间右侧的询问数量为 \(c\) 时,最小的代价和。

    初始化有 \(f_{i, i, c} = c \times a_i\)

    转移考虑枚举当前询问数 \(v\) ,如果 \(v \in (a_k, a_{k + 1}]\) ,那么有转移:

    \[\max(f_{L, k, c - 1} + v, f_{k + 1, R, c + 1} - v)\to f_{L, R, c} \]

    暴力转移 \(O(n^4)\)

    对于 \(80\%\) 的数据:

    观察 dp 转移的性质:

    \(\max(f_{L, k, c - 1} + v, f_{k + 1, R, c + 1} - v)\) 是一个关于 \(v\) 的单峰函数。

    证明:

    显然区间长度越大代价和越大,因此 \(f_{L, k, c - 1}\)\(v\) 增大单调递增,则 \(f_{L, k, c - 1} + v\)\(v\) 增大单调递增,同理 \(f_{k + 1, R, c + 1} - v\)\(v\) 增大单调递减,则 \(\max\) 为单峰函数。

    \(f_{L, R + 1, c}\) 的最优决策点在 \(f_{L, R, c}\) 的最优决策点右边。

    证明:

    仍然考虑 \(\max(f_{L, k, c - 1} + v, f_{k + 1, R, c + 1} - v)\) ,从 \(R\)\(R + 1\) ,上述式子变为 \(\max(f_{L, k, c - 1} + v, f_{k + 1, R + 1, c + 1} - v)\)

    左侧关于 \(v\) 的函数未发生变化,右侧由于 \(f_{k + 1, R + 1, c + 1} \ge f_{k + 1, R, c + 1}\) ,因此右侧函数会上升,简单画图不难发现,决策点只可能右移。

    使用决策单调性优化为 \(O(n^3)\)

    对于 \(100\%\) 的数据:

    \(V\) 为值域范围,\(|c|\) 的范围不会太大,约为 \(O(\log V)\) 级别。

    证明:

    感性理解,最朴素的想法是直接二分,此时的代价约为:

    \[(\tfrac{1}{2} + \tfrac{1}{4} + \tfrac{1}{8} + ...) V = V \]

    考虑区间 \([1, n]\) ,如果询问值 \(y < \tfrac{1}{3} V\) ,那么右侧区间产生的代价最小为 \(\tfrac{2}{3} V\) (如果 \(x\) 为最右侧数),左侧区间当前产生的最大代价为 \(\tfrac{1}{3}V\) ,以后产生的代价总和不超过直接二分的代价 \(\tfrac{1}{3}V\) ,因此左侧区间代价一定小于右侧区间代价。

    因此如果 \(y < \tfrac{1}{3} V\) ,我们将 \(y\) 调整至 \(y = \tfrac{1}{3}\) 一定不劣。

    因此询问点 \(y\) 一定在区间 \([\tfrac{1}{3}V, \tfrac{2}{3}V]\) 内。

    这样操作容易发现区间长度每次变为原先的 \(\tfrac{2}{3}\) ,因此总询问次数不超过 \(O(\log V)\) 次,即 \(|c|\) 不超过 \(O(\log V)\)

    经过实践,猜测 \(|c|\) 实际是 \(O(\tfrac{\log V}{\log\log V})\) 级别的。

    因此使用决策单调性优化,限制 \(|c|\) 的范围,枚举 \(k\) 计算最优的询问值 \(v\) ,可以很好地优化 dp 。

    总复杂度为 \(O(n^2\log V)\)

  • 牛客题解

总结

  • \(T1\) 因不会算结构体变量空间和提交前没有再编译一遍,挂了 \(100pts\)
    • 不然的话就只比 @jijidawang\(1 \min 39s\) \(AC\)
  • \(T2\)
    • 提交前没有再编译一遍,挂了 \(60pts\)
      • 我再也不相信 C/C++ 的报错了。。。
    • 下发了 testlib.h 和自测版 Special Judge ,赛时“参考”了自测版 Special Judge\(Tarjan\) 代码。
  • \(T3\) 结束前 \(10 \min\) 前原 \(O(nm^{2})\) 的做法才发现可以前缀和优化,过了小样例后就直接交了,忘删每次 \(O(m)\) 的清空了,挂了 \(25pts\)
  • \(T4\) 以为询问的数 \(y\) 必须也出自 \(\{ a \}\) ,挂了部分分。

后记

  • \(T1\) 原本在 @H_Kaguya 可持久化数据结构课件里做例题,没想到他和教练会为了检验我们有没有掌握将其放在了 \(T1\) 的位置。而 luogu P4690 [Ynoi2016] 镜中的昆虫 因被出到模拟赛里作为 \(T4\) ,他干脆没讲,只透露了会有一道珂朵莉树的题在模拟赛里等着我们,详见 暑假集训CSP提高模拟20 T4 P128. 穗

  • \(T2\) 数据出锅了。

  • \(T3\) 在前几天于题库中曾被公开过,组题人貌似泄题了也没管。

  • 有始有终

  • 题目背景夹带私活。

posted @ 2024-08-20 12:06  hzoi_Shadow  阅读(124)  评论(3编辑  收藏  举报
扩大
缩小