noip模拟16

A 草莓

真是唐题。原本以为是 dp,没想到是贪心。

然后搓了个 \(n^2\) 的 dp 以为正确性假假的,就优化成了 \(n^3\)。。。

dp 很简单,首先排序,设 \(dp[i][j]\) 表示横着掰到第 \(i\) 个,竖着拜到第 \(j\) 个的最小答案,然后从 \(dp[i-1][j]\)\(dp[i][j-1]\) 分别更新即可。

其实这就是贪心的思路,因为你的 dp 过程无非就是对于两个序列顺序一定时, \(x\)\(y\) 的选取更优策略。那直接把 \(x\)\(y\) 塞到一起排个序就好了。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int M=5e3+3,N=2e5+3;
int x[N],y[N];
int dp[M][M];
int n,m;
int ans=1e18;
inline bool cmp(int a,int b)
{
	return a>b;
}
int sumx[N],sumy[N];
signed main()
{
	freopen("guiltiness.in","r",stdin);
	freopen("guiltiness.out","w",stdout);
	cin>>n>>m;
	bool _1=1,_2=1,_=1;
	int cnt=0;
	for(int i=2;i<=n;i++) cin>>x[i];
	for(int i=2;i<=m;i++) cin>>y[i];
	ans=0;
	sort(x+2,x+n+1),sort(y+2,y+m+1);
	for(int i=n,j=m;i>1||j>1;)
	{
		if(x[i]>y[j]) ans+=(m-j+1)*x[i],i--;
		else ans+=(n-i+1)*y[j],j--;
	}
	cout<<ans;
	return 0;
}

B 三色

还是神秘 dp。弱化版是紫色。

考虑一个 \(n^3\) 的 dp,设 \(dp[i][j][k]\) 表示现在在 \(i\),即最前面的颜色在 \(i\),第二个颜色在 \(j\),第三个颜色在 \(k\) 的方案数。则通过刷表,可以从 \(dp[i][j][k]\) 分别转移到:

  • \(dp[i+1][j][k]\) 表示 \(i+1\) 放第一种颜色;

  • \(dp[i+1][i][k]\) 表示 \(i+1\) 放第二种颜色;

  • \(dp[i+1][i][j]\) 表示 \(i+1\) 放第三种颜色。

然后因为我们有 \(m\) 个限制,那么就需要对每次转移枚举的 \(i,j,k\) 进行限制。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int T;
const int N=504,mod=1e9+7;
int ***dp,*mi1,*mx3,*mi2,*mx2;
inline bool ck(int i,int j,int k)
{
	return j<mi1[i]&&k<mi2[i]&&j>=mx2[i]&&k>=mx3[i];
}
namespace q{
int n,m;
void work()
{
	cin>>n>>m;
	dp=new int **[n+1];
	mi1=new int [n+2],mx3=new int [n+2],mi2=new int [n+2],mx2=new int [n+3];
	for(int i=0;i<=n;i++)
	{
		dp[i]=new int *[n+1];
		for(int j=0;j<=n;j++) 
		{
			dp[i][j]=new int [n+1];
			for(int k=0;k<=n;k++) dp[i][j][k]=0;
		}
	 } 
	for(int i=0;i<=n;i++)mi1[i]=mi2[i]=0x3f3f3f3f,mx3[i]=mx2[i]=0;
	while(m--)
	{
		int l,r,x;cin>>l>>r>>x;
		if(x==1) mi1[r]=min(mi1[r],l);
		if(x==2) mi2[r]=min(mi2[r],l),mx2[r]=max(mx2[r],l);
		if(x==3) mx3[r]=max(mx3[r],l);
	}
	dp[0][0][0]=1;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<max(1,i);j++)
		{
			for(int k=0;k<max(1,j);k++)
			{
				if(ck(i,j,k))
				{
//					cout<<i<<" "<<j<<" "<<k<<"\n";
					dp[i+1][j][k]=(dp[i+1][j][k]+dp[i][j][k])%mod;
					dp[i+1][i][k]=(dp[i+1][i][k]+dp[i][j][k])%mod;
					dp[i+1][i][j]=(dp[i+1][i][j]+dp[i][j][k])%mod;
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<max(i,1);j++)
		{
			if(ck(n,i,j))ans=(ans+dp[n][i][j])%mod;
		}
	}
	cout<<ans<<"\n";
	delete [] mi1;delete []mx3;delete []mi2;delete []mx2;delete []dp;
}
}
signed main()
{
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--) q::work();
	return 0;
}

然后考虑优化。把每一层的 \(i\) 看作一个平面,那么每次转移都是从上一次的一个点,或者一条线的状态转移的。

然后就每次更新这些地方就行。具体可见 这篇博客

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int T,n,m;
namespace q{
	int *lsum,*csum,*minj,*maxj,*mink,*maxk,**f;
	int *t,lj,*rk,*lk;
	inline int sub(int x,int y) 
	{
		return ((x-y)%mod+mod)%mod;
	}
	inline void del(int j,int k)
	{
		lsum[j]=sub(lsum[j],f[j][k]);
		csum[k]=sub(csum[k],f[j][k]);
		f[j][k]=0;
	}
	void memst(int i)
	{
		for(int j=lj;j<i;j++)
		{
			if(j<minj[i]||j>maxj[i])
			{
				for(int k=0;k<=n;k++) del(j,k);
				lk[j]=n,rk[j]=0;
			}
			else
			{
				for(int k=lk[j];k<mink[i];k++) del(j,k);
				for(int k=rk[j];k>maxk[i];k--) del(j,k);
				lk[j]=max(lk[j],mink[i]);
				rk[j]=min(rk[j],maxk[i]);
			}
		}
		lj=max(lj,minj[i]);
	}
	void work()
	{
		cin>>n>>m;
		lsum=new int [n+1],csum=new int [n+1],minj=new int [n+1],maxj=new int [n+1],
		mink=new int [n+1],maxk=new int [n+1],t=new int [n+1],lk=new int [n+1],rk=new int [n+1];
		f=new int *[n+1];
		for(int i=0;i<=n;i++)
		{
			f[i]=new int [n+1];
			for(int j=0;j<=n;j++) f[i][j]=0;
			lsum[i]=csum[i]=minj[i]=mink[i]=0;
			maxj[i]=maxk[i]=n;
			lk[i]=0,rk[i]=n;
		}
		int l,r,x;
		while(m--)
		{
			cin>>l>>r>>x;
			if(x==1) maxj[r]=min(maxj[r],l-1);
			if(x==2) minj[r]=max(minj[r],l),maxk[r]=min(maxk[r],l-1);
			if(x==3) minj[r]=max(minj[r],l+1),mink[r]=max(mink[r],l);
		}
		lj=0;
		f[0][0]=lsum[0]=csum[0]=1;
		for(int i=1;i<=n;i++)
		{
			if((double)clock()/CLOCKS_PER_SEC>=0.96) return cout<<0,void();
			for(int k=0;k<i;k++) t[k]=(lsum[k]+csum[k])%mod;
			if(minj[i]<=i-1&&maxj[i]>=i-1)
			{
				for(int k=mink[i];k<=min(maxk[i],max(0,i-2));k++)
				{
					f[i-1][k]=(f[i-1][k]+t[k])%mod;
					csum[k]=(csum[k]+t[k])%mod;
					lsum[i-1]=(lsum[i-1]+t[k])%mod;
				}
			}
			memst(i);
		}
		int ans=0;
		for(int i=0;i<=n;i++) ans=(ans+lsum[i])%mod;
		cout<<ans<<"\n";
		delete []f,delete []lsum,delete []csum,delete []minj,delete []mink,delete []maxj,delete []maxk,delete []lk,delete []rk,delete []t;
	}
}
signed main()
{
	freopen("color.in","r",stdin);
	freopen("color.out","w",stdout);
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>T;
	while(T--)q::work();
	return 0;
}

C 博弈

根据题意,这三个数一定是向着减小差距的方向修改的。

考虑最简单的、对于一个三元组的 check。你会发现如果他们三个都不同的话,一定能赢。否则,如果三个数相等,那第一把就输了。那么 \(_i\) 互不相同的方案数就是 \(\binom{n}{3}\)

看来剩下的只剩下形如 \(a,a,b\) 这种形式的了。还是考虑多少次能够到达修改的临界点,发现当 \(\text{lowbit}(a-b)\) 的位数是偶数个才能满足。

\(\log\):对每一位开一个 map,对于所有 \(a_i\) 枚举 \(b\)

\(\log\):建一棵从下往上的 Trie 树。目前正在卡常。

D 后缀数组

\(60\) 分做法:

首先用 FHQ 维护那 \(m\) 个修改操作,时间复杂度 \(m\log n\)。实现是简单的,具体可以参考文艺平衡树。

然后,统计所有 \(rk[a[i]+1]<rk[a[i+1]+1]\) 的数量,答案是 \(2^k\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
const int N=1e5+4,mod=998244353;
int a[N];
int acnt;
struct TREE
{
	struct NODE
	{
		int l,r,val,siz,key;
		bool lz;
	}tree[N<<1];
	int cnt{},root{};
	inline int newnode(int val)
	{
		tree[++cnt].val = val,tree[cnt].siz = 1,tree[cnt].key = rand();
		return cnt;
	}
	inline void ins(int val){root = merge(root,newnode(val));}
	inline void pushup(int x){tree[x].siz = tree[tree[x].l].siz + tree[tree[x].r].siz + 1;}
    inline void pushdown(int x)
    {
        if(tree[x].lz)
        {
            tree[x].l ^= tree[x].r ^= tree[x].l ^= tree[x].r;
            tree[tree[x].l].lz ^= 1;tree[tree[x].r].lz ^= 1;
            tree[x].lz = false;
        }
    }
    inline void split(int x,int siz, int &l,int &r)
    {
        if(!x) l = r = 0;
        else
        {
            pushdown(x);
            if(tree[tree[x].l].siz < siz){ l = x;split(tree[x].r,siz-tree[tree[x].l].siz-1,tree[x].r,r);}
            else{r = x;split(tree[x].l,siz,l,tree[x].l);}
            pushup(x);
        }
    }
	int x,y,z;
    inline int merge(int x,int y)
    {
        if(!x || !y) return x + y;
        if(tree[x].key > tree[y].key)
        {
            pushdown(x);tree[x].r = merge(tree[x].r,y);
            pushup(x);return x;
        }
        else
        {
            pushdown(y);tree[y].l = merge(x,tree[y].l);
            pushup(y);return y;
        }
    }
	inline void reverse(int l,int r)
	{
	   split(root,l-1,x,y);
	   split(y,r-l+1,y,z);
	   tree[y].lz ^= 1;
	   root = merge(merge(x,y),z);
	}
	inline void work(int l,int r)
	{
		split(root,l-1,x,y);
		split(y,r-l+1,y,z);
		root = merge(y,merge(x,z));
	}
    inline void output(int x)
    {
       if(!x) return;
       pushdown(x);
       output(tree[x].l);
       a[++acnt] = tree[x].val;
       output(tree[x].r);
    }
}tr;
int rk[N],dp[N];
signed main()
{
	freopen("sa.in","r",stdin);
	freopen("sa.out","w",stdout);
	srand(time(NULL));
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++) tr.ins(i);
	while(m--)
	{
		int op,u,v;cin>>op>>u>>v;
		if(op==0) tr.work(u,v);
		else tr.reverse(u,v);
	}
	tr.output(tr.root);
	for(int i=1;i<=n;i++)rk[a[i]]=i;
	dp[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(rk[a[i]+1]>rk[a[i-1]+1])
		dp[i]=dp[i-1]*2%mod;
		else dp[i]=dp[i-1];
	}
	cout<<dp[n];
}
posted @ 2024-11-18 23:55  ccjjxx  阅读(6)  评论(0编辑  收藏  举报