8.11 训练总结

A. Flip it and Stick it

题面

神秘分讨题,交了 13 发才过。

首先注意到这个 \(|T|\le3\),显然因为 \(T\) 的种类很少,所以我们考虑分类讨论。

\(|T|=1\)

此时显然 \(T\) 只可能是 0 或 1。这个随便写啊。

代码:

void solve()
{
	if(t=="1")
	{
		for(auto i:s)
		{
			if(i=='1')
			{
				write(-1);
				return;
			}
		}
		write(0);
	}
	else
	{
		for(auto i:s)
		{
			if(i=='0')
			{
				write(-1);
				return;
			}
		}
		write(0);
	}
}

\(|T|=2\)

情况稍微有点复杂了。

我们考虑如果 \(T\) 的所有情况:显然只有 00、01、10、11。通过观察我们发现将 \(S\) 中的 0 和 1 反转并将 \(T\) 中的 0 和 1 反转答案是不变的,而将 \(S\) 反转并将 \(T\) 反转显然也是不变的,所以我们只需要考虑 01 和 11 的情况。

先来说 01:此时显然 \(S\) 最后会变成前面一串 1、后面一串 0 的形式,因此我们只需要将所有的 \(1\) 全部转到前面去就行,当然在开头的 1 不用转。

再来考虑 11:此时显然 \(S\) 会变成 0 和 1 交替的形式,那么 \(S\) 最多能容纳 \(\lceil\frac{|S|}{2}\rceil\) 个 1,如果 1 的个数比这个大那么直接判为 -1。

否则我们考虑如何让 \(S\) 中不存在 11,显然如果你出现了 11,那么总有办法可以使得一次操作就能拆除掉一个 11,所以答案就是 11 的个数。

代码:

void solve()
{
	if(t=="10")
	{
		reverse(s.begin(),s.end());
		t="01";
	}
	if(t=="00")
	{
		for(auto &i:s)
		{
			i^=1;
		}
		t="11";
	}
	if(t=="01")
	{
		int ans=0,len=0;
		for(int i=0;i<n;i++)
		{
			if(s[i]=='0')
			{
				if(i-len&&len)
				{
					ans++;
				}
				len=0;
			}
			else
			{
				len++;
			}
		}
		if(n-len&&len)
		{
			ans++;
		}
		write(ans);
	}
	if(t=="11")
	{
		int cnt=0;
		for(auto i:s)
		{
			if(i=='1')
			{
				cnt++;
			}
		}
		if(cnt>(n+1)/2)
		{
			write(-1);
			return;
		}
		int ans=0;
		for(int i=0;i<n-1;i++)
		{
			if(s[i]=='1'&&s[i+1]=='1')
			{
				ans++;
			}
		}
		write(ans);
	}
}

\(|T|=3\)

此时情况更复杂了。

我们还是考虑 \(T\) 有哪些情况:根据去重过后我们可以得到只剩下了 100、101、111 这三种。

我们先说 100:你会发现你每次反转最多可以去掉一个 100,因为如果你将两个 100 同时反转,那么介于两个 100 之间的第一个 1 必然会再次形成一个 100(如果你将后面的那个 100 整体反转了,那么前面的那个 1 又会形成一个 100),所以你每次反转最多去除掉一个 100,所以答案就是 100 的个数。

再说 101:你会发现你一次反转最多可以同时去掉两个 101,这个显然也是好构造的,所以答案就是 101 的个数除以 \(2\) 向上取整数。

最后说 111:你会想到出现 111 的地方必然会有一大串 1,而你每次最多可以去掉 \(2\) 个 1,所以答案就是每个连续 1 串的长度除以 \(2\)……吗?

我们考虑你取出来的两个 1 该放在什么位置:为了不再次构造 111,我们需要将这两个 1 放在 0 之间,那万一 0 之间没有空隙了呢?此时我们就只能一个一个拆,所以答案需要对 0 之间的剩余空隙取最小值。

当然你会发现如果 1 的数量大于 \(\lceil\frac{2n}{3}\rceil\),此时无解。

代码:

void solve()
{
	if(t=="010")
	{
		for(auto &i:s)
		{
			i^=1;
		}
		t="101";
	}
	if(t=="110")
	{
		reverse(s.begin(),s.end());
		t="011";
	}
	if(t=="011")
	{
		for(auto &i:s)
		{
			i^=1;
		}
		t="100";
	}
	if(t=="001")
	{
		reverse(s.begin(),s.end());
		t="100";
	}
	if(t=="000")
	{
		for(auto &i:s)
		{
			i^=1;
		}
		t="111";
	}
	if(t=="101")
	{
		int ans=0;
		for(int i=0;i<n-2;i++)
		{
			if(s[i]=='1'&&s[i+1]=='0'&&s[i+2]=='1')
			{
				ans++;
			}
		}
		write((ans+1)/2);
	}
	if(t=="100")
	{
		int ans=0;
		for(int i=0;i<n-2;i++)
		{
			if(s[i]=='1'&&s[i+1]=='0'&&s[i+2]=='0')
			{
				ans++;
			}
		}
		write(ans);
	}
	if(t=="111")
	{
		int cnt=0;
		for(auto i:s)
		{
			if(i=='1')
			{
				cnt++;
			}
		}
		if(cnt>(n*2+2)/3)
		{
			write(-1);
			return;
		}
		int len=0;
		cnt=0;
		for(int i=0;i<n;i++)
		{
			if(s[i]=='1')
			{
				len++;
			}
			else
			{
				if(len==0)
				{
					cnt++;
				}
				len=0;
			}
		}
		if(s[n-1]=='0')
		{
			cnt++;
		}
		len=0;
		int ans=0;
		for(int i=0;i<n;i++)
		{
			if(s[i]=='1')
			{
				len++;
			}
			else
			{
				if(len>2)
				{
					len-=2;
					int c=min(len/2,cnt);
					cnt-=c;
					len-=c*2;
					ans+=len+c;//如果没有空隙了就只能一个一个去除
				}
				len=0;
			}
		}
		if(len>2)
		{
			len-=2;
			int c=min(len/2,cnt);
			cnt-=c;
			len-=c*2;
			ans+=len+c;
		}
		len=0;
		write(ans);
	}
}

B. 画图

题面

暴力模拟题。

显然要合并线段。

将同一行、同一列的线段全部合并之后,这些线段必然会构造成 THUPC 的字样。

数一数会发现一共有 \(7\) 条横线、\(8\) 条竖线。但是我们并不知道顺序,因此我们可以暴力枚举顺序。

总复杂度大概是 \(O(8!\times7!)\),预计用时 2s,稍微剪枝一下就过了。

代码:

struct row{
	int l,r,y;
	bool operator < (const row &a)const
	{
		return y<a.y||y==a.y&&l<a.l;
	}
};
struct col{
	int d,u,x;
	bool operator < (const col &a)const
	{
		return x<a.x||x==a.x&&d<a.d;
	}
};
vector<row>rw;
vector<col>cl;
int n;
void merge()
{
	vector<row>v1;
	int len=rw.size();
	int l=rw[0].l,r=rw[0].r,x=rw[0].y;
	for(int i=1;i<len;i++)
	{
		if(rw[i].y!=rw[i-1].y)
		{
			v1.push_back((row){l,r,x});
			l=rw[i].l,r=rw[i].r,x=rw[i].y;
		}
		else
		{
			if(rw[i].l<=r)
			{
				r=max(r,rw[i].r);
			}
			else
			{
				v1.push_back((row){l,r,x});
				l=rw[i].l,r=rw[i].r,x=rw[i].y;
			}
		}
	}
	v1.push_back((row){l,r,x});
	rw=v1;
	vector<col>v2;
	len=cl.size();
	l=cl[0].d,r=cl[0].u,x=cl[0].x;
	for(int i=1;i<len;i++)
	{
		if(cl[i].x!=cl[i-1].x)
		{
			v2.push_back((col){l,r,x});
			l=cl[i].d,r=cl[i].u,x=cl[i].x;
		}
		else
		{
			if(cl[i].d<=r)
			{
				r=max(r,cl[i].u);
			}
			else
			{
				v2.push_back((col){l,r,x});
				l=cl[i].d,r=cl[i].u,x=cl[i].x;
			}
		}
	}
	v2.push_back((col){l,r,x});
	cl=v2;
}
int count(int la,int ra,int lb,int rb)
{
	int cnt=0;
	for(int i=la;i<=ra;i++)
	{
		for(int j=lb;j<=rb;j++)
		{
			cnt+=(rw[i].y>=cl[j].d&&rw[i].y<=cl[j].u&&cl[j].x>=rw[i].l&&cl[j].x<=rw[i].r);
		}
	}
	return cnt;
}
bool check()
{
	if(!(cl[0].u==rw[0].y&&cl[0].x>rw[0].l&&cl[0].x<rw[0].r))
	{
		return false;
	}
	if(!(rw[1].l==cl[1].x&&rw[1].r==cl[2].x&&rw[1].y>cl[1].d&&rw[1].y<cl[1].u&&rw[1].y>cl[2].d&&rw[1].y<cl[2].u))
	{
		return false;
	}
	if(!(rw[2].l==cl[3].x&&rw[2].r==cl[4].x&&rw[2].y==cl[3].d&&rw[2].y==cl[4].d))
	{
		return false;
	}
	if(!(rw[3].l==cl[5].x&&rw[3].r==cl[6].x&&rw[4].l==cl[5].x&&rw[4].r==cl[6].x&&rw[3].y==cl[5].u&&rw[3].y==cl[6].u&&rw[4].y==cl[6].d&&rw[4].y>cl[5].d))
	{
		return false;
	}
	if(!(rw[5].l==cl[7].x&&rw[6].l==cl[7].x&&rw[5].y==cl[7].u&&rw[6].y==cl[7].d&&rw[5].r==rw[6].r))
	{
		return false;
	}
	if(count(0,6,0,7)-count(0,0,0,0)-count(1,1,1,2)-count(2,2,3,4)-count(3,4,5,6)-count(5,6,7,7))
	{
		return false;
	}
	return true;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		int op=read(),l=read(),r=read(),x=read();
		if(op==0)
		{
			rw.push_back((row){l,r,x});
		}
		else
		{
			cl.push_back((col){l,r,x});
		}
	}
	sort(rw.begin(),rw.end());
	sort(cl.begin(),cl.end());
	if(rw.empty()||cl.empty())
	{
		write("No");
		return 0;
	}
	merge();
	if(rw.size()!=7||cl.size()!=8)
	{
		write("No");
		return 0;
	}
	sort(cl.begin(),cl.end());
	do
	{
		if(cl[1].d!=cl[2].d||cl[1].u!=cl[2].u||cl[3].d!=cl[4].d||cl[3].u!=cl[4].u)
		{
			continue;
		}
		sort(rw.begin(),rw.end());
		do
		{
			if(check())
			{
				write("Yes");
				return 0;
			}
		}while(next_permutation(rw.begin(),rw.end()));
	}while(next_permutation(cl.begin(),cl.end()));
	write("No");
	return 0;
}

C. Monotonicity

题面

简单 DP。

首先显然设 \(f_{i,j}\) 表示考虑到第 \(i\) 个数,大小关系是 \(s_j\) 时的最长长度。

转移可以很容易写出来:\(f_{i,j}=\max_{\operatorname{cmp}(a_l,a_i)=s_j}\{f_{l,(j-1)\bmod k+1}\}\)。

其中 \(\operatorname{cmp}\) 表示比较函数。

然后你会发现这个东西可以用线段树优化。

至于构造序列,我们直接记一个 \(g_{i,j}\) 表示 \(f_{i,j}\) 从哪转移过来,然后根据 \(g\) 构造就行了。

代码:

const int N=2e4+3,K=1e2+3;
struct info{
	int mx,x,y;
	void clear()
	{
		mx=-1e9,x=y=0;
	}
	void update(int s,int x,int y)
	{
		mx=s,this->x=x,this->y=y;
	}
};
struct seg_tree{
	struct Node{
		int ls,rs;
		info f;
	}node[N*7];
	int cnt=0,rt=0;
	void pushup(int pos,int ls,int rs)
	{
		if(!ls)
		{
			node[pos].f=node[rs].f;
		}
		else if(!rs)
		{
			node[pos].f=node[ls].f;
		}
		else
		{
			if(node[ls].f.mx>=node[rs].f.mx)
			{
				node[pos].f=node[ls].f;
			}
			else
			{
				node[pos].f=node[rs].f;
			}
		}
	}
	void update(int &pos,int l,int r,int x,int s,int i,int j)
	{
		if(!pos)
		{
			pos=++cnt;
			node[pos].f.clear();
		}
		if(l==r)
		{
			if(s>node[pos].f.mx)
			{
				node[pos].f.update(s,i,j);
			}
			return;
		}
		int mid=l+r>>1;
		if(x<=mid)
		{
			update(node[pos].ls,l,mid,x,s,i,j);
		}
		else
		{
			update(node[pos].rs,mid+1,r,x,s,i,j);
		}
		pushup(pos,node[pos].ls,node[pos].rs);
	}
	info query(int pos,int l,int r,int ql,int qr)
	{
		if(!pos||ql>qr)
		{
			return (info){(int)(-1e9),0,0};
		}
		if(l>=ql&&r<=qr)
		{
			return node[pos].f;
		}
		int mid=l+r>>1;
		info ans;
		ans.clear();
		if(ql<=mid)
		{
			info t=query(node[pos].ls,l,mid,ql,qr);
			if(t.mx>ans.mx)
			{
				ans=t;
			}
		}
		if(qr>mid)
		{
			info t=query(node[pos].rs,mid+1,r,ql,qr);
			if(t.mx>ans.mx)
			{
				ans=t;
			}
		}
		return ans;
	}
}tr[K];
int n,k,a[N],s[K],f[N][K];
pair<int,int>g[N][K];
void print(int x,int y)
{
	if(!x)
	{
		return;
	}
	print(g[x][y].first,g[x][y].second);
	write(a[x]);
	putchar(' ');
}
signed main()
{
	n=read(),k=read();
	int mn=1e9,mx=-1e9;
	read(a+1,a+n+1);
	for(int i=1;i<=n;i++)
	{
		mn=min(mn,a[i]);
		mx=max(mx,a[i]);
	}
	for(int i=1;i<=k;i++)
	{
		char c;
		cin>>c;
		if(c=='>')
		{
			s[i]=1;
		}
		else if(c=='<')
		{
			s[i]=-1;
		}
	}
	memset(f,-0x3f,sizeof(f));
	for(int i=1;i<=n;i++)
	{
		f[i][0]=1;
		g[i][0]=make_pair(0,0);
		for(int j=1;j<=k;j++)
		{
			info ans;
			if(s[j]==-1)
			{
				ans=tr[j-1].query(tr[j-1].rt,mn,mx,mn,a[i]-1);
			}
			else if(s[j]==0)
			{
				ans=tr[j-1].query(tr[j-1].rt,mn,mx,a[i],a[i]);
			}
			else
			{
				ans=tr[j-1].query(tr[j-1].rt,mn,mx,a[i]+1,mx);
			}
			f[i][j]=ans.mx+1;
			g[i][j]=make_pair(ans.x,ans.y);
			if(j==1)
			{
				if(s[j]==-1)
				{
					ans=tr[k].query(tr[k].rt,mn,mx,mn,a[i]-1);
				}
				else if(s[j]==0)
				{
					ans=tr[k].query(tr[k].rt,mn,mx,a[i],a[i]);
				}
				else
				{
					ans=tr[k].query(tr[k].rt,mn,mx,a[i]+1,mx);
				}
				if(ans.mx+1>f[i][j])
				{
					f[i][j]=ans.mx+1;
					g[i][j]=make_pair(ans.x,ans.y);
				}
			}
		}
		for(int j=0;j<=k;j++)
		{
			if(f[i][j]>0)
			{
				tr[j].update(tr[j].rt,mn,mx,a[i],f[i][j],i,j);
			}
		}
	}
	int ans=0,x=0,y=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<=k;j++)
		{
			if(f[i][j]>ans)
			{
				ans=f[i][j];
				x=i,y=j;
			}
		}
	}
	write(ans);
	putchar('\n');
	print(x,y);
	return 0;
}

但是我 MLE 了并没有卡过。

因此我们考虑优化这个算法:显然这个 DP 具有贪心性(读者自证),所以我们可以去掉第二维,此时 \(f_i\) 直接保存最优解,然后你会发现这个只需要两个 BIT 就能解决。

代码:

const int N=2e4+6,K=1e2+6,MX=1e6+6;
struct BIT{
	int len,tr[MX];
	#define lowbit(x) (x&-x)
	void init(int x)
	{
		len=x;
	}
	void update(int x,int y)
	{
		for(int i=x;i<=len;i+=lowbit(i))
		{
			tr[i]=max(tr[i],y);
		}
	}
	int query(int x)
	{
		int ans=0;
		for(int i=x;i;i-=lowbit(i))
		{
			ans=max(ans,tr[i]);
		}
		return ans;
	}
}tr1,tr2;
int n,k,a[N],s[K],f[N],b[MX];
void print(int x)
{
	if(f[x]==1)
	{
		write(a[x]);
		putchar(' ');
		return;
	}
	int t=(f[x]-2)%k+1;
	for(int i=x-1;i>=1;i--)
	{
		if(f[i]+1==f[x]&&(s[t]==1&&a[i]>a[x]||s[t]==0&&a[i]==a[x]||s[t]==-1&&a[i]<a[x]))
		{
			print(i);
			break;
		}
	}
	write(a[x]);
	putchar(' ');
}
signed main()
{
	n=read(),k=read();
	int mx=-1e9;
	read(a+1,a+n+1);
	for(int i=1;i<=n;i++)
	{
		mx=max(mx,a[i]);
	}
	for(int i=1;i<=k;i++)
	{
		char c;
		cin>>c;
		if(c=='>')
		{
			s[i]=1;
		}
		else if(c=='<')
		{
			s[i]=-1;
		}
	}
	tr1.init(mx),tr2.init(mx);
	for(int i=1;i<=n;i++)
	{
		f[i]=max({tr1.query(a[i]-1),tr2.query(mx-a[i]),b[a[i]]})+1;
		int t=(f[i]-1)%k+1;
		if(s[t]==0)
		{
			b[a[i]]=max(b[a[i]],f[i]);
		}
		else if(s[t]==-1)
		{
			tr1.update(a[i],f[i]);
		}
		else
		{
			tr2.update(mx+1-a[i],f[i]);
		}
	}
	int ans=0,x=0;
	for(int i=1;i<=n;i++)
	{
		if(f[i]>ans)
		{
			ans=f[i];
			x=i;
		}
	}
	write(ans);
	putchar('\n');
	print(x);
	return 0;
}

D. Cubes

题面

观察性质题。

首先我们会想到若 \(a_i+a_j>k\),那么 \(i,j\) 的相对位置不变。

那我们考虑什么时候 \(a_i+a_j>k\)。

有一种经典想法是以 \(\lfloor\frac{k}{2}\rfloor\) 为界线来分类讨论,我们来试试:

  1. 若 \(a_i,a_j\le\lfloor\frac{k}{2}\rfloor\),此时 \(a_i+a_j\le k\)。

  2. 若 \(a_i,a_j>\lfloor\frac{k}{2}\rfloor\),此时 \(a_i+a_j>k\)。

我们将大于 \(\lfloor\frac{k}{2}\rfloor\) 的叫做重元素,小于等于 \(\lfloor\frac{k}{2}\rfloor\) 的叫做轻元素,那么我们就会得到:

  1. 所有的重元素之间的相对顺序不变。

  2. 重元素之间的轻元素可以随意换位置。

现在的问题就是这个轻元素的活动范围到底有多大。

显然每个轻元素的活动范围可以求出来,那这些活动范围有什么用呢?此时我们来观察性质:

  • 性质一:若 \(a_i=a_j\),则 \(i\) 的活动范围与 \(j\) 的活动范围要么相同,要么不交。

  • 性质二:若 \(a_i\not=a_j\),那么 \(i\) 的活动范围与 \(j\) 的活动范围要么是包含关系,要么不交。

  • 性质三:若 \(a_i<a_j\),那么 \(i\) 的活动范围要么包含 \(j\) 的活动范围,要么与 \(j\) 的活动范围不交。

上面的性质比较显然,读者可以自证。

因此我们就得到了一个做法:将所有数从大到小排序,然后尝试将当前数插入进去:如果当前数是重元素,那么直接插入到相应位置;如果是轻元素,那么将活动范围一致的相同数全部合并在一起并插入到范围里。第三条性质告诉我们我们不需要考虑前面的元素可能在区间内可能不在的情况,于是这个问题就很简单了。

代码:

const int N=3e5+6;
struct BIT1{
	int len,tr[N];
	#define lowbit(x) (x&-x)
	void init(int x)
	{
		len=x;
	}
	void update(int x,int y)
	{
		for(int i=x;i;i-=lowbit(i))
		{
			tr[i]=max(tr[i],y);
		}
	}
	int query(int x)
	{
		if(!x)
		{
			return 0;
		}
		int ans=0;
		for(int i=x;i<=len;i+=lowbit(i))
		{
			ans=max(ans,tr[i]);
		}
		return ans;
	}
}tr1;
struct BIT2{
	int len,tr[N];
	#define lowbit(x) (x&-x)
	void init(int x)
	{
		len=x;
		for(int i=1;i<=len;i++)
		{
			tr[i]=1e18;
		}
	}
	void update(int x,int y)
	{
		for(int i=x;i;i-=lowbit(i))
		{
			tr[i]=min(tr[i],y);
		}
	}
	int query(int x)
	{
		if(!x)
		{
			return 1e18;
		}
		int ans=1e18;
		for(int i=x;i<=len;i+=lowbit(i))
		{
			ans=min(ans,tr[i]);
		}
		return ans;
	}
}tr2;
struct BIT{
	int len,tr[N];
	#define lowbit(x) (x&-x)
	void init(int x)
	{
		len=x;
	}
	void update(int x,int y)
	{
		for(int i=x;i<=len;i+=lowbit(i))
		{
			tr[i]+=y;
		}
	}
	int query(int x)
	{
		int ans=0;
		for(int i=x;i;i-=lowbit(i))
		{
			ans+=tr[i];
		}
		return ans;
	}
}tr;
const int mod=1e9+7;
int n,k,a[N],L[N],R[N],fac[N],inv[N];
vector<int>v,id[N];
vector<pair<int,int>>vv[N];
int qpow(int x,int y)
{
	int z=1;
	while(y)
	{
		if(y&1)
		{
			z=z*x%mod;
		}
		x=x*x%mod;
		y>>=1;
	}
	return z;
}
int C(int x,int y)
{
	return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
signed main()
{
	n=read(),k=read();
	read(a+1,a+n+1);
	fac[0]=1;
	for(int i=1;i<=n;i++)
	{
		fac[i]=fac[i-1]*i%mod;
	}
	inv[n]=qpow(fac[n],mod-2);
	for(int i=n-1;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%mod;
	}
	a[0]=a[n+1]=1e18;
	for(int i=0;i<=n+1;i++)
	{
		v.push_back(a[i]);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());
	tr1.init(v.size());
	tr1.update(v.size(),0);
	for(int i=1;i<=n;i++)
	{
		if(a[i]>k/2)
		{
			int it=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1;
			tr1.update(it,i);
		}
		else
		{
			int it=upper_bound(v.begin(),v.end(),k-a[i])-v.begin()+1;
			L[i]=tr1.query(it);
		}
	}
	tr2.init(v.size());
	tr2.update(v.size(),n+1);
	for(int i=n;i>=1;i--)
	{
		if(a[i]>k/2)
		{
			int it=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1;
			tr2.update(it,i);
		}
		else
		{
			int it=upper_bound(v.begin(),v.end(),k-a[i])-v.begin()+1;
			R[i]=tr2.query(it);
		}
	}
	for(int i=1;i<=n;i++)
	{
		int it=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1;
		id[it].push_back(i);
		vv[it].push_back(make_pair(L[i],R[i]));
	}
	int len=v.size();
	tr.init(n+1);
	tr.update(n+1,1);
	int ans=1;
	for(int i=len-2;i>=0;i--)
	{
		if(v[i]>k/2)
		{
			for(auto j:id[i+1])
			{
				tr.update(j,1);
			}
		}
		else
		{
			map<pair<int,int>,int>cnt;
			for(auto j:vv[i+1])
			{
				cnt[j]++;
			}
			for(auto j:vv[i+1])
			{
				if(!cnt[j])
				{
					continue;
				}
				int num=tr.query(j.second)-tr.query(j.first)-1;
				ans=ans*C(num+cnt[j],cnt[j])%mod;
				tr.update(j.second,cnt[j]);
				cnt[j]=0;
			}
		}
	}
	write(ans);
	return 0;
}

E. Desant

题面

人类智慧啊!!!

首先我们很容易想到一个暴力 \(O(2^n)\) 的做法。

但是 \(n\) 很大,那我们该怎么办呢?显然可以 meet-in-the-middle,但是我不会。

下面做法来自题解:

假设当前我在第 \(i\) 个位置,假设后面的数是 \(x_1,x_2,\dots,x_m\),那么我们注意到我们只关心 \([1,x1),(x_1,x_2),\dots\) 的部分,因此我们可以把这些段设进 DP 里。

我们发现这个东西的复杂度是每一段的段长加一的积,根据小学奥数知识我们可以知道这个东西最大是 \(3^{\frac{n}{3}}\),显然是可过的。

转移也是非常简单:我们只需要知道下一个数并判断它是否会被加入进去。

为了方便我们将后面的维度全部 Hash 压缩以下。

代码:

const int N=46;
int n,a[N],len[N],now[N],w[N][N],num[N][N];
vector<int>f[N],g[N];
signed main()
{
	n=read();
	read(a+1,a+n+1);
	for(int i=0;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			num[i][++len[i]]=a[j];
		}
		num[i][++len[i]]=n+1;
		sort(num[i]+1,num[i]+len[i]+1);
		int t=1;
		for(int j=1;j<=len[i];j++)
		{
			w[i][j]=t;
			t*=(num[i][j]-num[i][j-1]);
		}
		f[i].resize(t,1e9);
		g[i].resize(t,0);
	}
	f[0][0]=0;
	g[0][0]=1;
	for(int i=0;i<n;i++)
	{
		int id=0;
		for(int j=1;j<=len[i];j++)
		{
			if(a[i+1]==num[i][j])
			{
				id=j;
				break;
			}
		}
		int l=f[i].size();
		for(int j=0;j<l;j++)
		{
			int t=j;
			for(int k=len[i];k>=1;k--)
			{
				now[k]=t/w[i][k];
				t%=w[i][k];
			}
			int cnt=0;
			for(int k=id+1;k<=len[i];k++)
			{
				cnt+=now[k];
			}
			int nx=0;
			for(int k=1;k<id;k++)
			{
				nx+=now[k]*w[i+1][k];
			}
			for(int k=id+2;k<=len[i];k++)
			{
				nx+=now[k]*w[i+1][k-1];
			}
			nx+=(now[id]+now[id+1])*w[i+1][id];
			if(f[i][j]<f[i+1][nx])
			{
				f[i+1][nx]=f[i][j];
				g[i+1][nx]=g[i][j];
			}
			else if(f[i][j]==f[i+1][nx])
			{
				g[i+1][nx]+=g[i][j];
			}
			nx+=w[i+1][id];
			if(f[i][j]+cnt<f[i+1][nx])
			{
				f[i+1][nx]=f[i][j]+cnt;
				g[i+1][nx]=g[i][j];
			}
			else if(f[i][j]+cnt==f[i+1][nx])
			{
				g[i+1][nx]+=g[i][j];
			}
		}
	}
	for(int i=1;i<=n;i++)
	{
		write(vector<int>{f[n][i],g[n][i]});
		putchar('\n');
	}
	return 0;
}
posted @ 2026-08-11 22:14  cwxcplh  阅读(13)  评论(0)    收藏  举报