NOIP2024加赛6

NOIP2024加赛6

\(T1\) P323. 草莓 \(60pts\)

  • 部分分

    • \(60pts\)
      • 先将 \(\{ x \},\{ y \}\) 降序排序,状态转移方程为 \(f_{i,j}=\min(f_{i-1,j}+x_{i}(j+1),f_{i,j-1}+y_{j}(i+1))\) ,边界为 \(f_{0,0}=0\) ,最终有 \(f_{n-1,m-1}\) 即为所求。
      • 若费用提前计算则需要将 \(\{ x \},\{ y \}\) 升序排序并做前缀和,状态转移方程为 \(f_{i,j}=\min(f_{i-1,j}+sumx_{i},f_{i,j-1}+sumy_{j})\) ,边界为 \(f_{0,0=0}\) ,最终有 \(f_{n-1,m-1}+sumx_{n-1}+sumy_{m-1}\) 即为所求。
    点击查看代码
    ll x[200010],y[200010],f[2][200010];
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("guiltiness.in","r",stdin);
    	freopen("guiltiness.out","w",stdout);
    #endif
    	ll n,m,i,j;
    	cin>>n>>m;
    	n--;
    	m--;
    	for(i=1;i<=n;i++)
    	{
    		cin>>x[i];
    	}
    	for(i=1;i<=m;i++)
    	{
    		cin>>y[i];
    	}
    	sort(x+1,x+n+1);
    	sort(y+1,y+m+1);
    	for(i=1;i<=n;i++)
    	{
    		x[i]+=x[i-1];
    	}
    	for(i=1;i<=m;i++)
    	{
    		y[i]+=y[i-1];
    	}
    	memset(f,0x3f,sizeof(f));
    	f[0][0]=0;
    	for(i=0;i<=n;i++)
    	{
    		for(j=0;j<=m;j++)
    		{
    			f[(i+1)&1][j]=0x3f3f3f3f3f3f3f3f;
    		}
    		for(j=0;j<=m;j++)
    		{
    			f[(i+1)&1][j]=min(f[(i+1)&1][j],f[i&1][j]+y[j]);
    			f[i&1][j+1]=min(f[i&1][j+1],f[i&1][j]+x[i]);
    		}
    	}
    	cout<<f[n&1][m]+x[n]+y[m]<<endl;
    	return 0;
    }
    
  • 正解

    • 上述做法因为需要求解在网格图上的最短路,只能另寻他法。
    • 考虑临项交换,观察到最终操作序列上相邻的 \(x_{i},y_{j}\) 交换成 \(y_{j},x_{i}\) 更优当且仅当 \(y_{j}>x_{i}\) ,将 \(\{ x \},\{ y \}\) 放到一起降序排序即可。
    点击查看代码
    ll cnt[2];
    vector<pair<ll,ll> >a;
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("guiltiness.in","r",stdin);
    	freopen("guiltiness.out","w",stdout);
    #endif
    	ll n,m,x,ans=0,i;
    	cin>>n>>m;
    	n--;
    	m--;
    	for(i=1;i<=n+m;i++)
    	{
    		cin>>x;
    		a.push_back(make_pair(x,i>n));
    	}
    	sort(a.begin(),a.end());
    	reverse(a.begin(),a.end());
    	for(i=0;i<a.size();i++)
    	{
    		ans+=a[i].first*(cnt[a[i].second^1]+1);
    		cnt[a[i].second]++;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    

\(T2\) P324. 三色 \(0pts\)

  • 原题:QOJ 1286. Ternary String Counting

  • 部分分

    • 子任务 \(1,2\)
      • 类似 luogu P11233 [CSP-S 2024] 染色 ,设 \(f_{i,j,k}\) 表示 \([1,i]\) 中第一个与 \(a_{i}\) 颜色不同的是 \(a_{j}\) ,第一个与 \(a_{i},a_{j}\) 颜色互不相同的数是 \(a_{k}\) 的方案数。转移时分讨 \(a_{i+1}=a_{i}/a_{j}/a_{k}\) 刷表法转移即可。边界为 \(f_{1,0,0}=3\)
      • 当访问到一个限制时及时去除不合法状态。
      • 最终有 \(\sum\limits_{j=0}^{n-1}\sum\limits_{k=0}^{\max(0,j-1)}f_{n,j,k}\) 即为所求。
      • 时间复杂度为 \(O(n^{3}+m)\)
    点击查看代码
    const int p=1000000007;
    int f[2][5010][5010];
    vector<pair<int,int> >q[5010];
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("color.in","r",stdin);
    	freopen("color.out","w",stdout);
    #endif
    	int t,n,m,l,r,x,ans,lj,lk,rj,rk,i,j,k,h,v;
    	scanf("%d",&t);
    	for(v=1;v<=t;v++)
    	{
    		ans=0;
    		cin>>n>>m;
    		for(i=1;i<=n;i++)
    		{
    			q[i].clear();
    		}
    		for(i=1;i<=m;i++)
    		{	
    			scanf("%d%d%d",&l,&r,&x);
    			q[r].push_back(make_pair(l,x));
    		}
    		memset(f,0,sizeof(f));
    		f[1][0][0]=3;
    		for(i=1;i<=n;i++)
    		{
    			lj=lk=0;
    			rj=rk=0x3f3f3f3f;
    			for(j=0;j<q[i].size();j++)
    			{
    				switch(q[i][j].second)
    				{
    					case 1:
    						rj=min(rj,q[i][j].first-1);
    						break;
    					case 2:
    						lj=max(lj,q[i][j].first);
    						rk=min(rk,q[i][j].first-1);
    						break;
    					case 3:
    						lk=max(lk,q[i][j].first);
    						break;
    					default:
    						break;
    				}
    			}
    			for(j=0;j<=i;j++)
    			{
    				for(k=0;k<=max(0,j-1);k++)				
    				{
    					if(j<lj||j>rj||k<lk||k>rk)
    					{
    						f[i&1][j][k]=0;
    					}
    					f[(i+1)&1][j][k]=0;
    				}
    			}
    			for(j=0;j<=i-1;j++)
    			{
    				for(k=0;k<=max(0,j-1);k++)
    				{
    					f[(i+1)&1][j][k]=(f[(i+1)&1][j][k]+f[i&1][j][k])%p;
    					f[(i+1)&1][i][k]=(f[(i+1)&1][i][k]+f[i&1][j][k])%p;
    					f[(i+1)&1][i][j]=(f[(i+1)&1][i][j]+f[i&1][j][k])%p;
    				}
    			}
    		}
    		for(j=0;j<=n-1;j++)
    		{
    			for(k=0;k<=max(0,j-1);k++)
    			{
    				ans=(ans+f[n&1][j][k])%p;
    			}
    		}
    		cout<<ans<<endl;
    	}
    	return 0;
    }
    
  • 正解

    • 仍考虑 luogu P11233 [CSP-S 2024] 染色 的优化方式,将 \(j,k\) 看做矩阵。当 \(i\) 转移到 \(i+1\) 时,分讨 \(a_{i+1}=a_{i}/a_{j}/a_{k}\) 分别对应复制到 \(i+1\) 处/将每列累加到 \(i+1\) 行的对应位置/将每行累加到 \(i+1\) 列的对应位置。前者直接滚动数组继承,后两者可以看做新加入一行/列。考虑维护每一行、每一列的前缀和。
    • 遇到限制时等价于保留合法矩阵,将不合法状态置零。
    • 发现处理完限制后保留的都是一个区间,使用 deque 维护合法状态即可,使得每个数仅被加入、删除 \(O(1)\) 次。
    • 时间复杂度为 \(O(n^{2}+m)\)
    点击查看代码
    const int p=1000000007;
    int s[5010],lj[5010],rj[5010],lk[5010],rk[5010],sh[5010],sl[5010],f[5010][5010];
    deque<int>q[5010];
    void del(int j,int l,int r)
    {
    	while(q[j].empty()==0&&q[j].front()<l)
    	{
    		sh[j]=(sh[j]-f[j][q[j].front()]+p)%p;
    		sl[q[j].front()]=(sl[q[j].front()]-f[j][q[j].front()]+p)%p;
    		q[j].pop_front();
    	}
    	while(q[j].empty()==0&&q[j].back()>r)
    	{
    		sh[j]=(sh[j]-f[j][q[j].back()]+p)%p;
    		sl[q[j].back()]=(sl[q[j].back()]-f[j][q[j].back()]+p)%p;
    		q[j].pop_back();
    	}
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("color.in","r",stdin);
    	freopen("color.out","w",stdout);
    #endif
    	int t,n,m,l,r,x,ans,i,j,k,v;
    	scanf("%d",&t);
    	for(v=1;v<=t;v++)
    	{
    		scanf("%d%d",&n,&m);
    		ans=0;
    		for(i=0;i<=n;i++)
    		{
    			lj[i]=lk[i]=0;
    			rj[i]=rk[i]=i-1;
    			sh[i]=sl[i]=0;
    			q[i].clear();
    			for(j=0;j<=max(0,i-1);j++)
    			{
    				q[i].push_back(j);
    			}
    			for(j=0;j<=n;j++)
    			{
    				f[i][j]=0;
    			}
    		}	
    		for(i=1;i<=m;i++)
    		{
    			scanf("%d%d%d",&l,&r,&x);
    			switch(x)
    			{
    				case 1:
    					rj[r]=min(rj[r],l-1);
    					break;
    				case 2:
    					lj[r]=max(lj[r],l);
    					rk[r]=min(rk[r],l-1);
    					break;
    				case 3:
    					lk[r]=max(lk[r],l);
    					break;
    				default:
    					break;
    			}
    		}
    		f[0][0]=sh[0]=sl[0]=3;
    		for(i=1;i<=n;i++)
    		{
    			for(j=0;j<=i-1;j++)
    			{
    				if(lj[i]<=j&&j<=rj[i])
    				{
    					del(j,lk[i],rk[i]);
    				}
    				else
    				{
    					del(j,j+1,0);//全部清空
    				}
    			}
    			if(i!=n)
    			{
    				for(k=0;k<=i-1;k++)
    				{
    					f[i][k]=(sh[k]+sl[k])%p;
    					sh[i]=(sh[i]+f[i][k])%p;
    					sl[k]=(sl[k]+f[i][k])%p;
    				}
    			}
    		}
    		for(i=0;i<=n;i++)
    		{
    			ans=(ans+sh[i])%p;
    		}
    		printf("%d\n",ans);
    	}
    	return 0;
    }
    

\(T3\) P325. 博弈 \(0pts\)

  • 原题: QOJ 8077. Madeline and Badeline

  • 选出的三个数为 \(a,b,c(a<b<c)\) 时,有 Madeline 必胜。

    • \(a,c\) 关于 \(b\) 对称,那么 Madeline 直接操作 \(a,c\) 使其等于 \(b\) 即可获胜。
    • 否则,以 \(a'=a-(c-b)>0,b,b\) 为例( \(b,b,c'=c-(b-a)>0\) 同理)。若在进入该状态后 Badeline 必胜,则 Madeline 直接仿照原 Badeline 的操作方式操作(多操作几步使其到达 Badeline 必胜的第一步情况)即可获胜;否则因为 Badeline 必败,故 Madeline 必胜。
  • 选出的三个数中有两个数相同时,以 \(a,a,b(a<b)\) 为例。此时 Madeline 只能将 \(a,b\) 操作成 \(\frac{a+b}{2}\) (如果无法操作必败), Badeline 继续这种操作方式。若 \(|a-b|\) 最低位的 \(1\) 是第偶数位 Madeline 必胜,即 __builtin_ffs(abs(a-b))%2==0

  • 选出的三个数都相同时 Madeline 必败。

  • \(\{ a \}\) 倒着插入 \(01Trie\) 树上统计叶子个数即可。

    点击查看代码
    unordered_map<ll,ll>vis;
    unordered_map<ll,ll>::iterator it;
    struct Trie
    {
    	int son[30000010][4],siz[30000010],rt_sum=1;
    	void clear()
    	{
    		for(int i=1;i<=rt_sum;i++)
    		{
    			son[i][0]=son[i][1]=son[i][2]=son[i][3]=siz[i]=0;
    		}
    		rt_sum=1;
    	}
    	int build_rt()
    	{
    		rt_sum++;
    		return rt_sum;
    	}
    	void insert(ll s)
    	{
    		int x=1;
    		for(int i=0;i<=60;i+=2)
    		{
    			if(son[x][(s>>i)&3]==0)
    			{
    				son[x][(s>>i)&3]=build_rt();
    			}
    			siz[x]++;
    			x=son[x][(s>>i)&3];
    		}
    		siz[x]++;
    	}
    	ll query(ll s)
    	{
    		int x=1;
    		ll ans=0;
    		for(int i=0;i<=60;i+=2)
    		{
    			ans+=(siz[son[x][((s>>i)&3)^1]]+siz[son[x][((s>>i)&3)^3]]);
    			x=son[x][(s>>i)&3];
    		}
    		return ans;
    	}
    }T;
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("game.in","r",stdin);
    	freopen("game.out","w",stdout);
    #endif
    	ll t,n,x,ans,i,j;
    	scanf("%lld",&t);
    	for(j=1;j<=t;j++)
    	{
    		T.clear();
    		vis.clear();
    		scanf("%lld",&n);
    		ans=n*(n-1)*(n-2)/6;
    		for(i=1;i<=n;i++)
    		{
    			scanf("%lld",&x);
    			T.insert(x);
    			vis[x]++;
    		}
    		for(it=vis.begin();it!=vis.end();it++)
    		{
    			ans-=it->second*(it->second-1)/2*T.query(it->first);
    			ans-=it->second*(it->second-1)*(it->second-2)/6;
    		}
    		printf("%lld\n",ans);
    	}
    	return 0;
    }
    

\(T4\) P326. 后缀数组 \(0pts\)

  • 原题: CodeChef TASUFFIX-Very Long Suffix Array

    点击查看 std 代码
    #include<iostream>
    #include<stdio.h>
    #include<ctype.h>
    #include<random>
    #include<map>
    #include<math.h>
    #define ll long long
    #define ld long double
    #define fi first
    #define se second
    #define pii pair<int,int>
    #define lowbit(x) ((x)&-(x))
    #define popcount(x) __builtin_popcount(x)
    #define inf 0x3f3f3f3f
    #define infll 0x3f3f3f3f3f3f3f3f
    #define umap unordered_map
    #define all(x) x.begin(),x.end()
    #define mk make_pair
    #define pb push_back
    #define ckmax(x,y) x=max(x,y)
    #define ckmin(x,y) x=min(x,y)
    #define rep(i,l,r) for(int i=l;i<=r;++i)
    #define per(i,r,l) for(int i=r;i>=l;--i)
    #define N 100005
    using namespace std;
    inline int read(){
    	int x=0,f=0; char ch=getchar();
    	while(!isdigit(ch)) f|=(ch==45),ch=getchar();
    	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    	return f?-x:x;
    }
    const int mo=998244353;
    inline void red(int &x){x>=mo?x-=mo:0;}
    inline int qpow(int x,int b){
    	int res=1;
    	for(;b;x=1LL*x*x%mo,b>>=1) if(b&1) res=1LL*res*x%mo;
    	return res;
    }
    struct FHQ{
    	int ls,rs,sze,cnt,sum,lazy;
    	pii v;
    }T[N*2];
    int rt,pool;
    mt19937 rnd(114514);
    inline int newNode(int l,int r){
    	T[++pool].sze=1,T[pool].cnt=T[pool].sum=max(r-l+1,l-r+1),T[pool].v={l,r};
    	return pool;
    }
    inline void pushup(int k){
    	T[k].sze=T[T[k].ls].sze+T[T[k].rs].sze+1;
    	T[k].sum=T[T[k].ls].sum+T[T[k].rs].sum+T[k].cnt;
    }
    inline void pushdown(int k){
    	if(!T[k].lazy) return;
    	T[k].lazy=0;
    	swap(T[T[k].ls].v.fi,T[T[k].ls].v.se);
    	swap(T[T[k].rs].v.fi,T[T[k].rs].v.se);
    	swap(T[T[k].ls].ls,T[T[k].ls].rs);
    	swap(T[T[k].rs].ls,T[T[k].rs].rs);
    	T[T[k].ls].lazy^=1;
    	T[T[k].rs].lazy^=1;
    }
    int U;
    void split(int k,int &x,int &y,int s){
    	if(!k) return (void)(x=y=0);
    	pushdown(k);
    	if(T[T[k].ls].sum+T[k].cnt<=s){
    		x=k;
    		split(T[k].rs,T[k].rs,y,s-T[T[k].ls].sum-T[k].cnt);
    		U+=T[T[k].ls].sum+T[k].cnt;
    	}
    	else{
    		y=k;
    		split(T[k].ls,x,T[k].ls,s);
    	}
    	pushup(k);
    }
    int merge(int x,int y){
    	if(!x || !y) return x+y;
    	pushdown(x),pushdown(y);
    	if(rnd()%(T[x].sze+T[y].sze)<T[x].sze){
    		T[x].rs=merge(T[x].rs,y);
    		pushup(x);return x;
    	}
    	else{
    		T[y].ls=merge(x,T[y].ls);
    		pushup(y);return y;
    	}
    }
    int n,m;
    pii b[2*N];
    void get(int k){
    	pushdown(k);
    	if(T[k].ls) get(T[k].ls);
    	b[++m]=T[k].v;
    	if(T[k].rs) get(T[k].rs);
    }
    map<int,int> rk;
    signed main(){
    	freopen("sa.in","r",stdin);
    	freopen("sa.out","w",stdout);
    	n=read(),m=read();
    	rt=newNode(1,n);
    	for(int i=1;i<=m;++i){
    		int op=read(),l=read(),r=read();
    		int x,y,z;
    		U=0;
    		split(rt,x,y,l-1);
    		U=l-1-U;
    		if(U){
    			int now=y;
    			while(T[now].ls){
    				pushdown(now);
    				T[now].sum-=U;
    				now=T[now].ls;
    			}
    			T[now].sum-=U,T[now].cnt-=U;
    			if(T[now].v.fi<=T[now].v.se){
    				int mid=T[now].v.fi+U-1;
    				x=merge(x,newNode(T[now].v.fi,mid));
    				T[now].v.fi=mid+1;
    			}
    			else{
    				int mid=T[now].v.fi-U+1;
    				x=merge(x,newNode(T[now].v.fi,mid));
    				T[now].v.fi=mid-1;
    			}
    		}
    		U=0;
    		split(y,y,z,r-l+1);
    		U=(r-l+1)-U;
    		if(U){
    			int now=z;
    			while(T[now].ls){
    				pushdown(now);
    				T[now].sum-=U;
    				now=T[now].ls;
    			}
    			T[now].sum-=U,T[now].cnt-=U;
    			if(T[now].v.fi<=T[now].v.se){
    				int mid=T[now].v.fi+U-1;
    				y=merge(y,newNode(T[now].v.fi,mid));
    				T[now].v.fi=mid+1;
    			}
    			else{
    				int mid=T[now].v.fi-U+1;
    				y=merge(y,newNode(T[now].v.fi,mid));
    				T[now].v.fi=mid-1;
    			}
    		}
    		if(op==0){
    			rt=merge(y,merge(x,z));	
    		}
    		else{
    			swap(T[y].v.fi,T[y].v.se);
    			swap(T[y].ls,T[y].rs);
    			T[y].lazy^=1;
    			rt=merge(x,merge(y,z));
    		}
    	}
    	m=0;
    	get(rt);
    	// for(int i=1;i<=m;++i) fprintf(stderr,"%d %d\n",b[i].fi,b[i].se);
    	int pre=0;
    	for(int i=1;i<=m;++i){
    		rk[b[i].fi]=pre+1;
    		pre+=abs(b[i].fi-b[i].se)+1;
    		rk[b[i].se]=pre;
    	}
    	int ans=-1,lst=-1;
    	pre=0;
    	for(int i=1;i<=m;++i){
    		if(b[i].fi<=b[i].se){
    			if(b[i].fi!=b[i].se){
    				if(pre+2>lst) ans++;
    			}
    			else if(rk[b[i].fi+1]>lst) ans++;
    			pre+=b[i].se-b[i].fi+1;
    			if(b[i].fi!=b[i].se){
    				if(rk[b[i].se+1]>pre) ans++;
    			}
    			lst=rk[b[i].se+1];
    		}
    		else{
    			if(rk[b[i].fi+1]>lst) ans++;
    			if(pre+1>rk[b[i].fi+1]) ans++;
    			pre+=b[i].fi-b[i].se+1;
    			lst=pre-1;
    		}
    		ans+=max(0,abs(b[i].fi-b[i].se)-1);
    	}
    	cout<<qpow(2,ans);
    	return 0;
    }
    
    

总结

  • \(T1\) 口胡的费用提前计算一开始想假了,将横竖的边权搞反了。

后记

  • \(T3\) 题面过于狗屎,没有说明行动过程中改变之后的数值仍为整数。
posted @ 2024-11-18 21:06  hzoi_Shadow  阅读(52)  评论(2编辑  收藏  举报
扩大
缩小