做题记录 00001 号

ABC301F

对于这种限制性强的,我们可以把它拆分为多个部分,比如这里我们分为四个状态:没有 D,没有 DD,没有 DDo,没有 DDoS,然后 dp 转移。

但是两个转移限制较强,发现可以直接通过处理前 i 位的大写字母直接得到没有 DD 的状态,所以把没有 D 状态舍去。

剩下三个状态称为状态 1,2,3,考虑转移,设 \(dp[i][id]\) 为前 i 个字母不出现状态 id 的子序列的方案数。

对于状态 1,我们处理出当前位置 i 前面哪些大写字母出现过,如果 si 是大写字母,且前面出现过就直接为 0。

否则如果是小写字母就直接转移 \(dp[i][1]=dp[i-1][1]。\)

如果是问号 或者没出现过的大写字母,我们统计前 \(i\) 位里所有问号的选择。

设有 \(num\) 个问号,有 \(sum\) 个没出现的大写字母,我们枚举 \(j\) 个 问号变大写字母,剩下的变小写。

首先乘上 \(C_{num}^j\) 即从 \(num\) 个问号里面选择 \(j\) 个变大写字母,这 \(j\) 个乘上 \(A_{sum}^j\) 即从 \(sum\) 个未出现的大写字母中选择 \(j\) 个,这里是排列。然后乘上 \(26^{num-j}\) 即剩下的可以随便选择任意的小写字母。

对于状态 2,如果当前是大写字母,它可以从 \(dp[i-1][2]\) 转移过来,这里不从 \(dp[i-1][1]\) 转移过来是因为不包含 DDo 的情况肯定比不包含 DD 的情况更广泛,所以前者已经把后者包含了;如果是小写字母,可以从 \(dp[i-1][1]\) 转移过来,对于问号两者皆可,从 \(dp[i-1][1]+dp[i-1][2]\) 转移过来,并且乘上一个 26 的系数,即前者可以选择任意一个小写字母,后者可以选择任意一个大写字母。为什么前者不能乘上它所没有的大写字母数量呢?我们要从问号的角度去讨论:如果它变成小写字母,那么它只能从状态 1 转移,不然可以从状态 2 转移,那么就也是一样的道理,\(dp[i-1][2]\) 把 \(dp[i-1][1]\) 的这种情况包含了。注意状态 2 是不含 DDo,那么它也可以包含不含 DD 的情况。

对于状态 3,也是同理的,假如是小写字母,从 \(dp[i-1][3]\) 转移过来;假如是大写字母,从 \(dp[i-1][2]\) 转移过来;假如是问号,从两者转移过来皆可,并乘上 26 的系数。

然后就做完了。

点击查看代码
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 500005
#define M 998244353
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
ll inv[N],sum[N],isum[N],sum26[N];
ll dp[N][4];
bitset<26> vis;
ll A(int n,int m)
{
	return sum[m]*isum[m-n]%M;
}
ll C(int n,int m)
{
	return sum[m]*isum[n]%M*isum[m-n]%M;
}
int main()
{
	int n;
	string s;
	cin>>s,n=s.size(),s=" "+s;
	dp[0][1]=dp[0][2]=dp[0][3]=1;
	sum[0]=sum[1]=inv[0]=inv[1]=isum[0]=isum[1]=1,sum26[0]=1,sum26[1]=26;
	for (rnt i=2;i<=300000;i++) sum[i]=sum[i-1]*i%M,inv[i]=(M-M/i)*inv[M%i]%M,isum[i]=isum[i-1]*inv[i]%M,sum26[i]=sum26[i-1]*26%M;
	for (rnt i=1,num=0,sum=26,flag=0;i<=n;i++)
		if (s[i]=='?')
		{
			num++;
			if (flag) dp[i][1]=0;
			else
				for (rnt j=0;j<=min(num,sum);j++)
					(dp[i][1]+=C(j,num)*A(j,sum)%M*sum26[num-j]%M)%=M;
			dp[i][2]=(dp[i-1][1]+dp[i-1][2])%M*26%M;
			dp[i][3]=(dp[i-1][2]+dp[i-1][3])%M*26%M;
		}
		else
		{
			if ('A'<=s[i] && s[i]<='Z')
			{
				if (!vis[s[i]-'A']) vis[s[i]-'A']=1,sum--;
				else flag=1;
			}
			if (flag) dp[i][1]=0;
			else
				for (rnt j=0;j<=min(num,sum);j++)
					(dp[i][1]+=C(j,num)*A(j,sum)%M*sum26[num-j]%M)%=M;
			if ('A'<=s[i] && s[i]<='Z') dp[i][2]=dp[i-1][2],dp[i][3]=dp[i-1][2];
			else dp[i][2]=dp[i-1][1],dp[i][3]=dp[i-1][3];
		}
	write(dp[n][3]);
	return 0;
}

ABC180F

看一下条件,第一个没什么大用,第二个说明这个图是一堆链和环构成的,第三个限制很大,我们考虑改为最大值小于等于 \(L\),和小于等于 \(L-1\),分别 dp,相减即可。

我们先考虑最大值小于等于 \(L\) 的情况,另一个也同理。设 \(dp[i][j]\) 为图里有 \(i\) 个点和 \(j\) 条边,我们枚举一个连通块大小为 \(k\)(\(k<=L\)),那么对于这个连通块,要么是链要么是环。

  • 是链:\(dp[i][j]\) 可以从 \(dp[i-k][j-k+1]\) 转移,后者此时剩余 \(n-i+k\) 个点,取出 \(k\) 个点,但是发现会有重复,比如有点集 \(\left\{1,2,3,4,5 \right\}\),我们取出 \(\left\{1,2 \right\}\),剩下 \(\left\{3,4,5\right\}\) 为一个连通块,和取出 \(\left\{3,4,5\right\}\),剩下 \(\left\{1,2 \right\}\) 重复了。这里有个经典套路,我们定一个点,一定会在某个连通块里即可,一般定集合的最小点。那么这里方案数就是 \(C_{n-i+k-1}^{k-1}\) 个,然后点的顺序随机,乘上 \(k!\),但是发现 \(\left\{1,2,3,4\right\}\) 和 \(\left\{4,3,2,1\right\}\) 是同理的,即正着和反着相同,所以再除以 2 即可。

  • 是环:\(dp[i][j]\) 可以从 \(dp[i-k][j-k]\) 转移,方案数仍然是 \(C_{n-i+k-1}^{k-1}\) 个,点的顺序随意,那么这就是一个圆排列,排列个数为 \((k-1)!\),然后由于反着来和正着相同,所以也要除以 2。

另外注意链大小为 1 和环大小为 2 时种类都有两种,由于没有自环,所以环大小不能为 1,dp 即可,复杂度 \(O(nmL)\)。

点击查看代码
//Just Sayori
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 305
#define M 1000000007
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
ll inv[N],sum[N],isum[N];
ll dp[N][N];
ll C(int n,int m)
{
	return sum[m]*isum[n]%M*isum[m-n]%M;
}
ll work(int n,int m,int l)
{
	dp[0][0]=1;
	for (rnt i=1;i<=n;i++)
		for (rnt j=0;j<=m;j++)
			dp[i][j]=0;
	for (rnt i=1;i<=n;i++)
		for (rnt j=0;j<=m;j++)
		{
			for (rnt k=1;k<=min(l,min(i,j+1));k++)
				(dp[i][j]+=dp[i-k][j-k+1]*C(k-1,n-i+k-1)%M*sum[k]%M*(k==1?1:inv[2])%M)%=M;
			for (rnt k=2;k<=min(l,min(i,j));k++)
				(dp[i][j]+=dp[i-k][j-k]*C(k-1,n-i+k-1)%M*sum[k-1]%M*(k==2?1:inv[2])%M)%=M;
		}
	return dp[n][m];
}
int main()
{
	int n=read(),m=read(),l=read();
	sum[0]=sum[1]=inv[0]=inv[1]=isum[0]=isum[1]=1;
	for (rnt i=2;i<=300;i++) sum[i]=sum[i-1]*i%M,inv[i]=(M-M/i)*inv[M%i]%M,isum[i]=isum[i-1]*inv[i]%M;
	write(((work(n,m,l)-work(n,m,l-1))%M+M)%M);
	return 0;
}

CF620F

还在想什么巧妙的 \(\log\) 做法,结果你告诉我这个 5e4 的 \(n\) 范围和 \(10\) 秒的时限?

让我们想一想根号算法啊,由于我不会分块,就只能想到莫队了,维护最大值不好删除贡献,想到回滚莫队。然后思考如何处理贡献。

处理出前缀异或和 \(sum[i]=1\oplus2……\oplus i\),那么\(f(a_x,a_y)=sum[a_y]\oplus sum[a_x-1](a_x<=a_y)\),对于新加入的点 \(j\),我们找到一个 \(x\) 使之最大,对于求异或最大,自然地想到字典树维护,建一棵 \(sum[a_i]\) 的字典树和一棵 \(sum[a_i-1]\) 的字典树,每次从前者找异或上 \(sum[a_j-1]\) 最大的值,再从后者找异或上 \(sum[a_j]\) 最大的值,与当前答案比较,再将点加入,回退时去字典树上减即可。但是发现这里有 \(a_x<=a_y\) 的限制啊,怎么办?我们发现字典树的形态很像线段树啊(但不知道是不是 Leafy Tree,应该不是),那么它也可以支持 push_up 操作,我们对于两个字典树,前者树上维护子树里 \(a_i\) 最大的值,后者维护最小的值,那么就可以像线段树二分一样找了。注意可以只选自己。

但是删除时该怎么维护最值呢?答案是那就不管了,直接清空,然后逐级上传,如果子树空了,那么就设为极值。

所以我们从大到小枚举二进制位数构建字典树,然后做回滚莫队即可,复杂度应该是 \(O((n+q)\sqrt{n}\log{V})\) 的。

好像做法有问题?块长开大就过了,nm,赤刀石了。

其实有简单好做的 \(O(n^2)\) 做法,对于每个点 \(i\),我们求出 \(f[j](i\le j)\),表示 \(i\sim j\) 的最大值,对于每个询问,如果其包含 \(i\),那么尝试用 \(f[r[j]]\) 更新它。

代码:

点击查看代码
//Just Sayori
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#include <cmath>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 1000005
#define M 1000000007
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
int num,tot,last;
int a[N],b[N],ans[N],sum[N];
struct node
{
	int l,r,id;
	bool operator <(node o)
	{
		return b[l]^b[o.l]?l<o.l:r<o.r;
	}
}q[N];
#define delete del
struct trie1
{
	int cnt=1;
#define ch(x,i) ((i)?s[x].lc:s[x].rc)
#define fa(x) s[x].fa
#define sz(x) s[x].sz
#define mn(x) s[x].mn
	struct node
	{
		int lc,rc,fa,sz,mn;
		node()
		{
			mn=M;
		}
	}s[N<<2];
	void insert(int x,int w,int u=1)//id=0:sum[a[x]-1],id=1:sum[a[x]]
	{
		for (rnt i=20;i>=0;i--)
		{
			int v=(x>>i)&1;
			if (!ch(u,v)) ch(u,v)=++cnt,fa(ch(u,v))=u;
			u=ch(u,v),sz(u)++,mn(u)=min(mn(u),w);
		}
	}
	void delete(int x,int u=1)
	{
		for (rnt i=20;i>=0;i--)
		{
			int v=(x>>i)&1;
			u=ch(u,v);
		}
		for (rnt i=0;i<=20;i++)
		{
			mn(u)=min(mn(ch(u,0)),mn(ch(u,1)));
			if (!(--sz(u))) mn(u)=M;
			u=fa(u);
		}
	}
	int query(int x,int w,int ans=0,int u=1)
	{
		for (rnt i=20;i>=0;i--)
		{
			int v=((x>>i)&1)^1;
			if (sz(ch(u,v)) && mn(ch(u,v))<=w)
				u=ch(u,v),ans|=(1<<i);
			else if (sz(ch(u,v^1)) && mn(ch(u,v^1))<=w)
				u=ch(u,v^1);
			else break;
		}
		return ans;
	}
}t1;
struct trie2
{
#define ch(x,i) ((i)?s[x].lc:s[x].rc)
#define fa(x) s[x].fa
#define sz(x) s[x].sz
#define mx(x) s[x].mx
	int cnt=1;
	struct node
	{
		int lc,rc,fa,sz,mx;
		node()
		{
			mx=-M;
		}
	}s[N<<2];
	void insert(int x,int w,int u=1)//id=0:sum[a[x]-1],id=1:sum[a[x]]
	{
		for (rnt i=20;i>=0;i--)
		{
			int v=(x>>i)&1;
			if (!ch(u,v)) ch(u,v)=++cnt,fa(ch(u,v))=u;
			u=ch(u,v),sz(u)++,mx(u)=max(mx(u),w);
		}
	}
	void delete(int x,int u=1)
	{
		for (rnt i=20;i>=0;i--)
		{
			int v=(x>>i)&1;
			u=ch(u,v);
		}
		for (rnt i=0;i<=20;i++)
		{
			mx(u)=max(mx(ch(u,0)),mx(ch(u,1)));
			if (!(--sz(u))) mx(u)=-M;
			u=fa(u);
		}
	}
	int query(int x,int w,int ans=0,int u=1)
	{
		for (rnt i=20;i>=0;i--)
		{
			int v=((x>>i)&1)^1;
			if (sz(ch(u,v)) && mx(ch(u,v))>=w) 
				u=ch(u,v),ans|=(1<<i);
			else if (sz(ch(u,v^1)) && mx(ch(u,v^1))>=w)
				u=ch(u,v^1);
			else break;
		}
		return ans;
	}
}t2;
inline void add(int x)
{
	num=max(max(num,a[x]),max(t1.query(sum[a[x]],a[x]),t2.query(sum[a[x]-1],a[x])));
	t1.insert(sum[a[x]-1],a[x]),t2.insert(sum[a[x]],a[x]);
}
int main()
{
	int n=read(),m=read(),len=650;
	for (rnt i=1;i<=1000000;i++) sum[i]=sum[i-1]^i;
	for (rnt i=1;i<=n;i++) a[i]=read(),b[i]=(i-1)/len+1;
	for (rnt i=1,l,r;i<=m;i++) q[i]={read(),read(),i};
	sort(q+1,q+m+1);
	for (rnt i=1,x=1,l=1,r=0,p;x<=b[n];x++)
	{
		num=last=0,p=min(len*x,n),l=p+1,r=p;
		for (;b[q[i].l]==x;i++)
		{
			if (b[q[i].l]==b[q[i].r])
			{
				int num=0;
				for (rnt j=q[i].l;j<=q[i].r;j++)
				{
					num=max(max(num,a[j]),max(t1.query(sum[a[j]],a[j]),t2.query(sum[a[j]-1],a[j])));
					t1.insert(sum[a[j]-1],a[j]),t2.insert(sum[a[j]],a[j]);
				}
				ans[q[i].id]=num;
				for (rnt j=q[i].l;j<=q[i].r;j++) t1.delete(sum[a[j]-1]),t2.delete(sum[a[j]]);
				continue;
			}
			while (r<q[i].r) add(++r);
			last=num;
			while (l>q[i].l) add(--l);
			ans[q[i].id]=num;
			while (l<p+1) t1.delete(sum[a[l]-1]),t2.delete(sum[a[l]]),l++; 
			num=last;
		}
		for (rnt i=l;i<=r;i++) t1.delete(sum[a[i]-1]),t2.delete(sum[a[i]]);
	}
	for (rnt i=1;i<=m;i++) write(ans[i]),pr(10);
	return 0;
}
//Just Sayori
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 1000005
#define M 1000000007
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
int a[N],f[N],l[N],r[N],ans[N],sum[N];
int main()
{
	int n=read(),m=read();
	for (rnt i=1;i<=n;i++) a[i]=read();
	for (rnt i=1;i<=1000000;i++) sum[i]=sum[i-1]^i;
	for (rnt i=1;i<=m;i++) l[i]=read(),r[i]=read();
	for (rnt i=1;i<=n;i++)
	{
		f[i]=a[i];
		for (rnt j=i+1;j<=n;j++) f[j]=max(f[j-1],sum[min(a[j],a[i])-1]^sum[max(a[i],a[j])]);
		for (rnt j=1;j<=m;j++)
			if (l[j]<=i && i<=r[j])
				ans[j]=max(ans[j],f[r[j]]);
	}
	for (rnt i=1;i<=m;i++) write(ans[i]),pr(10);
	return 0;
}

CF442D

每次加完点以后要求出一个很不好做的条件下的值,我们想到树形 dp。

彩色树指的是给树的每个边染色,对于一个点,它没有两个以上的同色边,且所有同色边构成一条路径。

发现对于一个节点,连儿子的边颜色相同显然不优,因为两条边不会同时出现在一条到根的路径上,并不会使答案减小。所以说一个边连接儿子另一个连接父亲更优,这样能让一个儿子的答案减小,那么选哪个呢?肯定是选值最大的儿子。那么剩下的儿子的值就要加一了。

设 \(dp[u]\) 为节点 \(u\) 的子树内的点通过子树内的边和 \(u\) 到父亲的边(根节点除外)产生的最大贡献,那么我们就要最小化 \(dp[1]\),那么有 \(dp[u]=max(mx_{son},cx_{son}+1)\),即要么是儿子中最大值或者次大值加一。这样复杂度是 \(O(n^2)\),考虑优化。

我们发现新加的节点只会影响一条链上的点,看起来还是 \(O(n^2)\) 的,但是实际上当加到一个位置,这个位置的值没有发生变化后,之后就不会有任何影响了。那么每个点被修改的次数就是加完点后的 \(dp[u]\),但是看起来仍然是 \(O(n^2)\) 的啊。

接下来就非常厉害了,还记得我们之前的结论,同色边连两个儿子肯定不优,而所有同色边构成一条路径,那么说明只有一种可能:树上的同色边构成了一条一条的链!那么我们的答案就变成了最小化所有一个点到根经过的不同颜色链的值中的最大值!学习了重链剖分后我们知道,一个点到根的重链只有 \(O(\log{n})\) 条,那么答案的上界也只有这么多。所以总的复杂度实际上是 \(O(n\log{n})\) 的!

然后直接暴力修改即可。

点击查看代码
/*
Just Sayori
不过有的时候,当你在心中有一片小乌云时。。
一首悲伤的诗能给这片乌云一个小小的拥抱。。
然后它就会变成一道漂亮的开心彩虹!

毁黄金黎明,弑师弑友
艾华斯附身,为法之书
知世界大战,不管不顾
为幻想杀手,建立学都
为了自己的计划,失忆上条
同意 6 级害死妹妹上万
隐瞒里幻意欲灭口右火
无视只眼害死上条无数
派脑干袭击上里大输特输
无视大热浪放任元素
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
十亿可能性,袭击大英
解伦敦结界,破坏核心
使上条当麻,遭受折磨
信一方通行,传位理事
对学都不管不顾,安娜搞事
超绝者叫牢登,因月之子
发现魔法理论,出爱丽丝
爱丽丝使上条当麻死亡
想要帮忙,却被克鲁兹吓哭
想和当麻旅馆搞黄色
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
九三零送外挂送走队友数多
坑一方为学都打工还债十亿
送 aaa,电击御坂美琴
竟不知女儿被恶魔欺骗
美国洛杉矶,大闹会议现场
做个天赋异禀的暴徒
选了这条路习惯危机四伏
余生刀枪不入
*/
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 1000005
#define M 1000000007
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
int cx[N],dp[N],fa[N],mx[N];
int main()
{
	int n=read();
	dp[1]=1;
	for (rnt i=2;i<=n+1;i++)
	{
		fa[i]=read(),dp[i]=1;
		for (rnt u=i;u;u=fa[u])
		{
			if (dp[u]>mx[fa[u]]) cx[fa[u]]=mx[fa[u]],mx[fa[u]]=dp[u];
			else if (dp[u]>cx[fa[u]]) cx[fa[u]]=dp[u];
			if (max(mx[fa[u]],cx[fa[u]]+1)==dp[fa[u]]) break;//如果无影响,直接退出
			dp[fa[u]]=max(mx[fa[u]],cx[fa[u]]+1);
		}
		write(mx[1]),pr(32);//对于 1 号点来说,它的最小化最大值就是 mx[1](因为它没有到父亲的边)
	}
	return 0;
}

P10517

一个经典的方法:将点全部按时间戳从小到大排序,然后求出它们到根节点的路径长度,那么两点之间路径长度就是它们到根节点路径长度和减去两倍的 LCA 的长度之差。依次枚举两个排序后相邻的点(包括首尾),我们得出的长度的一半就是所有可能成为的点了,减去询问的点即可。

如图:

发现除了点 1,其他算作答案的点都算了两次,除以二减去红点(即询问的点)以后我们还要加上一次点 1,而点 1 是第一个点和最后一个点的 LCA,如果 LCA 是圆点,就算作答案。

每次询问都这样求一遍,询问的时间复杂度可能是 \(O(n)\) 的,而每一次提供贡献的点只比上一次多或少一个点,所以我们可以有 set 维护,可能有点细节。
代码:

点击查看代码
//Just Sayori
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>
#include <cstring>
#define ll long long
#define rnt register int
#define gr getchar
#define pr putchar
#define N 400005
#define M 1000000007
using namespace std;

inline ll read()
{
    ll x = 0, f = 1;
    char ch = gr();
    while (ch < '0' || ch > '9')
        ch == '-' ? f = -1, ch = gr() : ch = gr();
    while (ch >= '0' && ch <= '9')
        x = (x << 3) + (x << 1) + (ch ^ 48), ch = gr();
    return x * f;
}

inline void write(ll x)
{
    static int sta[39], top = 0;
    if (x < 0)
        pr('-'), x = -x;
    do
        sta[++top] = x % 10, x /= 10;
    while (x);
    while (top)
        pr(sta[top--] ^ 48);
}

struct Edge
{
    struct edge
    {
        int v, next;
    } e[N << 2];
    int head[N << 1], cnt;
    inline void tdd(int u, int v)
    {
        e[++cnt] = {v, head[u]}, head[u] = cnt;
        e[++cnt] = {u, head[v]}, head[v] = cnt;
    }
} g, t;
int n, m, q, u, v, tp, ans, ans1, bcc, cnt, lca, tot;
int dfn[N], low[N], stack[N];
int a[N], d[N], fa[N], sz[N], to[N], top[N], bson[N], deep[N];
set<int> st;

void tarjan(int u)
{
    dfn[u] = low[u] = ++cnt;
    stack[++tp] = u;
    for (rnt i = g.head[u]; i; i = g.e[i].next)
    {
        int v = g.e[i].v;
        if (!dfn[v])
        {
            tarjan(v), low[u] = min(low[u], low[v]);
            if (low[v] == dfn[u])
            {
                bcc++;
                while (stack[tp + 1] != v)
                    t.tdd(stack[tp--], bcc);
                t.tdd(u, bcc);
            }
        }
        else
            low[u] = min(low[u], dfn[v]);
    }
}

void dfs1(int u, int f)
{
    d[u] = d[f] + (u <= n);
    fa[u] = f, sz[u] = 1;
    deep[u] = deep[f] + 1;
    for (rnt i = t.head[u]; i; i = t.e[i].next)
    {
        int v = t.e[i].v;
        if (v == f)
            continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if (sz[v] > sz[bson[u]])
            bson[u] = v;
    }
}

void dfs2(int u, int tp)
{
    top[u] = tp;
    dfn[u] = ++tot;
    to[tot] = u;
    if (!bson[u])
        return;
    dfs2(bson[u], tp);
    for (rnt i = t.head[u]; i; i = t.e[i].next)
    {
        int v = t.e[i].v;
        if (v == fa[u] || v == bson[u])
            continue;
        dfs2(v, v);
    }
}

int get_lca(int a, int b)
{
    while (top[a] != top[b])
    {
        if (deep[top[a]] < deep[top[b]])
            swap(a, b);
        a = fa[top[a]];
    }
    if (deep[a] > deep[b])
        swap(a, b);
    return a;
}

void check(int x, int f)
{
    if (a[x])
        st.insert(dfn[x]);
    auto now = st.find(dfn[x]);
    if (now == st.begin())
        if (st.size() != 1)
        {
            auto rs = next(now);
            int u = to[*now], v = to[*rs];
            ans += (d[u] + d[v] - 2 * d[get_lca(u, v)]) * f;
            v = to[*st.rbegin()];
            ans += (d[u] + d[v] - 2 * d[get_lca(u, v)]) * f, ans1 += (get_lca(u, v) <= n) * f;
            u = to[*rs];
            ans -= (d[u] + d[v] - 2 * d[get_lca(u, v)]) * f, ans1 -= (get_lca(u, v) <= n) * f;
        }
        else
            ans1 += 1 * f;
    else if (next(now) == st.end())
    {
        auto ls = prev(now);
        int u = to[*ls], v = to[*now];
        ans += (d[u] + d[v] - 2 * d[get_lca(u, v)]) * f;
        u = to[*st.begin()];
        ans += (d[u] + d[v] - 2 * d[get_lca(u, v)]) * f, ans1 += (get_lca(u, v) <= n) * f;
        v = to[*ls];
        ans -= (d[u] + d[v] - 2 * d[get_lca(u, v)]) * f, ans1 -= (get_lca(u, v) <= n) * f;
    }
    else
    {
        auto ls = prev(now), rs = next(now);
        int u = to[*ls], v = to[*rs], w = to[*now];
        ans -= (d[u] + d[v] - 2 * d[get_lca(u, v)]) * f;
        ans += (d[u] + d[w] - 2 * d[get_lca(u, w)]) * f;
        ans += (d[v] + d[w] - 2 * d[get_lca(v, w)]) * f;
    }
    if (!a[x])
        st.erase(st.find(dfn[x]));
}

int main()
{
    bcc = n = read(), m = read(), q = read();
    for (rnt i = 1; i <= m; i++)
        u = read(), v = read(), g.tdd(u, v);
    tarjan(1), dfs1(1, 0), dfs2(1, 1);
    for (rnt i = 1; i <= q; i++)
    {
        u = read();
        a[u] ^= 1;
        if (a[u])//添加
            check(u, 1);
        else//删除
            check(u, -1);
        write(n - ans / 2 - ans1), pr(10);
    }
    return 0;
}

ABC261H

设 \(dp[u][0]\) 为在 \(u\) 是甲回合的得分,甲要最小化,\(dp[u][1]\) 为在 \(u\) 是乙回合的得分,那么容易得到转移式子:

\[dp[u][0]=min(dp[v][1]+w(u,v))\\ dp[u][1]=max(dp[v][0]+w(u,v)) \]

但是环是一个大问题,甲是求最小值,可以 dijkstra 做,乙是求最大值,能无限走就无限走,所以要确定所有 \(v\) 才能转移。换言之,就是没法跑最长路,需要一个一个状态转移。最后 \(dp[s][0]=inf\) 游戏无法结束,否则可以。

证明一下正确性:\(dp[u][0]\) 的正确性显然,那么 \(dp[u][1]\) 的呢?由于 dijkstra,我们枚举一个点时这个点已经取到最小值(这里没有负环),所以 \(dp[u][1]\) 能被正确的 \(dp[v][0]\) 转移到,所以是正确的。

点击查看代码
/*
Just Sayori
不过有的时候,当你在心中有一片小乌云时。。
一首悲伤的诗能给这片乌云一个小小的拥抱。。
然后它就会变成一道漂亮的开心彩虹!

毁黄金黎明,弑师弑友
艾华斯附身,为法之书
知世界大战,不管不顾
为幻想杀手,建立学都
为了自己的计划,失忆上条
同意 6 级害死妹妹上万
隐瞒里幻意欲灭口右火
无视只眼害死上条无数
派脑干袭击上里大输特输
无视大热浪放任元素
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
十亿可能性,袭击大英
解伦敦结界,破坏核心
使上条当麻,遭受折磨
信一方通行,传位理事
对学都不管不顾,安娜搞事
超绝者叫牢登,因月之子
发现魔法理论,出爱丽丝
爱丽丝使上条当麻死亡
想要帮忙,却被克鲁兹吓哭
想和当麻旅馆搞黄色
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
九三零送外挂送走队友数多
坑一方为学都打工还债十亿
送 aaa,电击御坂美琴
竟不知女儿被恶魔欺骗
美国洛杉矶,大闹会议现场
做个天赋异禀的暴徒
选了这条路习惯危机四伏
余生刀枪不入
*/
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#include <queue>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 500005
#define M 0x3f3f3f3f3f3f3f3f
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
struct edge
{
	int v,w,next;
}e[N];
int head[N],cnt;
inline void add(int u,int v,int w)
{
	e[++cnt]={v,w,head[u]},head[u]=cnt;
}
int n,m,s;
int in[N];
ll dp[N][2];
bitset<2> vis[N];
struct node
{
	int u,id;
	ll dis;
	bool operator <(node o)const
	{
		return dis>o.dis;
	}
};
priority_queue<node> q;
void dijkstra(int u=0,int id=0)
{
	for (rnt i=1;i<=n;i++) dp[i][0]=M;
	for (rnt i=1;i<=n;i++)
		if (!in[i])
			dp[i][0]=dp[i][1]=0,q.emplace(node{i,0,0}),q.emplace(node{i,1,0});
	while (!q.empty())
	{
		u=q.top().u,id=q.top().id,q.pop();
		if (vis[u][id]) continue;
		vis[u][id]=1;
		for (rnt i=head[u];i;i=e[i].next)
		{
			int v=e[i].v,w=e[i].w;
			if (id)
			{
				if (dp[v][0]>dp[u][1]+w)
				{
					dp[v][0]=dp[u][1]+w;
					if (!vis[v][0]) q.emplace(node{v,0,dp[v][0]});
				}
			}
			else
			{
				dp[v][1]=max(dp[u][0]+w,dp[v][1]);
				if (!(--in[v])) q.emplace(node{v,1,dp[v][1]});
			}
		}
	}
}
int main()
{
	n=read(),m=read(),s=read();
	for (rnt i=1,u,v,w;i<=m;i++) u=read(),v=read(),w=read(),add(v,u,w),in[u]++;//注意是反着连边的
	dijkstra();
	if (dp[s][0]==M) puts("INFINITY");
	else write(dp[s][0]);
	return 0;
}

ABC221G

q1uple 大佬讲的神秘题。

如果一维我们该怎么做,发现是一个背包,相当于取 \(\pm d\),然后能否装 \(y-x\) 的大小,当然什么空间复杂度时间复杂度就别管了(逃)。

然后考虑二维,一个经典的 trick,我们发现把图旋转 45 度,然后每次增加 \(\pm\sqrt{2}d\),这样我们就将原坐标 \((x,y)\) 转换成了 \((x-y,x+y)\),发现这很像曼哈顿距离转切比雪夫距离(不过这里没有距离就是了),然后我们思考这样如何修改,那么就有:

\[(x+d,y)=(x-y+d,x+y+d)\\ (x-d,y)=(x-y-d,x+y-d)\\ (x,y+d)=(x-y-d,x+y+d)\\ (x,y-d)=(x-y+d,x+y-d)\\ \]

我们发现 \(x,y\) 都有 \(\pm d\) 的改变,而且两者是相互独立的,那么我们就可以二维变为两个一维,做可行性背包。但是正负号还是不好处理,我们直接让 \(x,y\) 加上 \(sumd\) ,相当于你全部选了 \(-d\),之后只用考虑 \(x,y+2*d\) 或者不变的情况了。但是为了卡空间(是的,不然开不下),我们要让 \(x,y\) 加完以后除以 2,为什么?我们这么想,如果 \(x,y\) 是偶数,那么 \(sumd\) 也必须要是偶数,为了保证到达 \(x,y\),可能有一些 \(d\) 要互相抵消,这些加在一起是偶数,剩下的要达到两个偶数,也要是偶数。那么假如 \(x,y\) 是奇数呢? 那么它们相减相加就是偶数,加上也是偶数的 \(sumd\) 不变。其中一个是奇数,那么相减相加是奇数,\(sumd\) 也是奇数,所以没问题。所以除以 2 以后相当于只用考虑 \(x,y+d\) 或者不变的情况了。

但是一般的 dp 数组开不下,还会超时,但是由于是可行性背包,可以 bitset 优化。

最后只剩下输出方案,我们发现,对于第 \(i\) 步到第 \(i+1\) 步,我们要么加上 \(d_{i+1}\) 要么不变。如果不变不行,那么肯定是加了 \(d_{i+1}\) 到这个位置的,减去就是没走这一步它所在的位置。然后对照上面的转换,\(-d\) 代表不变,\(+d\) 代表加上,然后输出对应的移动即可。

点击查看代码
/*
Just Sayori
不过有的时候,当你在心中有一片小乌云时。。
一首悲伤的诗能给这片乌云一个小小的拥抱。。
然后它就会变成一道漂亮的开心彩虹!

毁黄金黎明,弑师弑友
艾华斯附身,为法之书
知世界大战,不管不顾
为幻想杀手,建立学都
为了自己的计划,失忆上条
同意 6 级害死妹妹上万
隐瞒里幻意欲灭口右火
无视只眼害死上条无数
派脑干袭击上里大输特输
无视大热浪放任元素
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
十亿可能性,袭击大英
解伦敦结界,破坏核心
使上条当麻,遭受折磨
信一方通行,传位理事
对学都不管不顾,安娜搞事
超绝者叫牢登,因月之子
发现魔法理论,出爱丽丝
爱丽丝使上条当麻死亡
想要帮忙,却被克鲁兹吓哭
想和当麻旅馆搞黄色
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
九三零送外挂送走队友数多
坑一方为学都打工还债十亿
送 aaa,电击御坂美琴
竟不知女儿被恶魔欺骗
美国洛杉矶,大闹会议现场
做个天赋异禀的暴徒
选了这条路习惯危机四伏
余生刀枪不入
*/
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 3005
#define M 4000000
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
int d[N],ans[N];
bitset<M> dp[N];
bitset<10> a;
int main()
{
	int n=read(),a=read(),b=read(),x=a-b,y=a+b;
	ll sum=0;
	for (rnt i=1;i<=n;i++) d[i]=read(),sum+=d[i];
	if (abs(x)>sum || abs(y)>sum || ((x+sum)&1) || ((y+sum)&1)) return puts("No"),0;
	x=(x+sum)/2,y=(y+sum)/2,dp[0][0]=1;
	for (rnt i=1;i<=n;i++) dp[i]=dp[i-1]|(dp[i-1]<<d[i]);
	if (!dp[n][x] || !dp[n][y]) return puts("No"),0;
	for (rnt i=n-1;i>=0;i--)
	{
		int k=0;
		if (!dp[i][x]) x-=d[i+1],k+=1;
		if (!dp[i][y]) y-=d[i+1],k+=2;
		ans[i]=k;
	}
	puts("Yes");
	for (rnt i=0;i<=n-1;i++)
		switch(ans[i])
	{
	case 0:
		pr('L');
		break;
	case 1:
		pr('D');
		break;
	case 2:
		pr('U');
		break;
	case 3:
		pr('R');
	}
	return 0;
}

P14362

气笑了,以为很难的题结果居然不算难?

如果没有 \(k\) 个乡镇那么就是最小生成树板子,可以先做出来,剩下 \(n-1\) 条边。然后看到 \(k\le 10\),想到二进制枚举,每次有 \(k*n+n-1\) 条边,跑最小生成树,复杂度 \(O(m\log m+2^kkn\log{kn})\),按理来说会 T 几个点,不过也很好了。

但是我们发现把所有边都加进去,只排一次序,然后做最小生成树复杂度更好,是 \(O(mlogm+2^kkn)\) 的。具体来说,如果一个点的端点是没有枚举到的乡镇就跳过它,否则尝试加入,详见代码:

点击查看代码
/*
Just Sayori
不过有的时候,当你在心中有一片小乌云时。。
一首悲伤的诗能给这片乌云一个小小的拥抱。。
然后它就会变成一道漂亮的开心彩虹!

毁黄金黎明,弑师弑友
艾华斯附身,为法之书
知世界大战,不管不顾
为幻想杀手,建立学都
为了自己的计划,失忆上条
同意 6 级害死妹妹上万
隐瞒里幻意欲灭口右火
无视只眼害死上条无数
派脑干袭击上里大输特输
无视大热浪放任元素
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
十亿可能性,袭击大英
解伦敦结界,破坏核心
使上条当麻,遭受折磨
信一方通行,传位理事
对学都不管不顾,安娜搞事
超绝者叫牢登,因月之子
发现魔法理论,出爱丽丝
爱丽丝使上条当麻死亡
想要帮忙,却被克鲁兹吓哭
想和当麻旅馆搞黄色
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
九三零送外挂送走队友数多
坑一方为学都打工还债十亿
送 aaa,电击御坂美琴
竟不知女儿被恶魔欺骗
美国洛杉矶,大闹会议现场
做个天赋异禀的暴徒
选了这条路习惯危机四伏
余生刀枪不入
*/
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 2000005
#define M 1000000007
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
struct node
{
	int u,v,w;
	bool operator <(node o)
	{
		return w<o.w;
	}
}s[N],temp[N];
int c[N],fa[N];
int a[15][N];
bitset<15> vis;
int find(int a)
{
	return fa[a]==a?a:fa[a]=find(fa[a]);
}
int main()
{
	int n=read(),m=read(),k=read(),cnt=0;
	ll ans=0,sum=0;
	for (rnt i=1;i<=n;i++) fa[i]=i;
	for (rnt i=1;i<=m;i++) temp[i].u=read(),temp[i].v=read(),temp[i].w=read();
	sort(temp+1,temp+m+1);
	for (rnt i=1;i<=m;i++)
	{
		int fu=find(temp[i].u),fv=find(temp[i].v);
		if (fu==fv) continue;
		s[++cnt]=temp[i],ans+=temp[i].w,fa[fv]=fu;
	}
	for (rnt i=1;i<=k;i++)
	{
		c[i]=read();
		for (rnt j=1;j<=n;j++) a[i][j]=read(),s[++cnt]={i+n,j,a[i][j]};
	}
	sort(s+1,s+cnt+1);
	for (rnt sk=1;sk<1<<k;sk++)
	{
		sum=0,vis=0;
		for (rnt i=1;i<=n+k;i++) fa[i]=i;
		for (rnt i=1;i<=k;i++)
			if (sk>>(i-1)&1)
				sum+=c[i],vis[i]=1;
		for (rnt i=1;i<=cnt;i++)
		{
			int fu=find(s[i].u),fv=find(s[i].v);
			if ((s[i].u>n && !vis[s[i].u-n]) || (fu==fv)) continue;
			sum+=s[i].w,fa[fv]=fu;
		}
		ans=min(ans,sum);
	}
	write(ans);
	return 0;
}

P2157

第一眼看上去感觉很不可做啊,\(n\le 2000\) 很小,或许可以 dp,但是 dp 顺序难以确定,选择点还要看前面点的要求,还有逆天的 \((a \operatorname{or} b)-(a \operatorname{and} b)\)(顺带一提,这个等于 \(a \operatorname{xor} b\)),感觉无法完成啊。

但是我们发现,每个人的忍耐度很小,最大只有 7,如果把这个处理了,那么其他可能就相对好做了,那么这么小的数据范围怎么做呢?所以想到状压 dp。设 \(dp[i][j][k]\) 为前面的人都打到了饭,当前考虑第 \(i\) 个人,\(i\) 以及后面七个人的状态(是否打饭)的二进制串,上一个打饭的人是 \(j\) 的最小值,但是 \(2000\times 2000\times 2^8=1024000000\) 肯定是爆空间了的,考虑如何压缩,第一维也许可以滚动数组,然后我们看到第三维,发现实际上这个打饭的人肯定在 \(i\) 的前八个人、自身和后七个人里面,所以实际上范围只有 \(-8\sim7\),表示是 \(i+k\) 的位置,当然实际上我们的下标要偏移 8。

然后就可以考虑转移了,假如 \(j\) 的第零位是 1(这里注意 \(i\) 的状态是第零位,即 \(2^0\)) ,那么可以转移到 \(dp[i+1][j>>1][k-1]\),什么意思呢?因为 \(i\) 打了饭,所以前 \(i\) 个人打完了饭,所以转移到 \(i+1\),由于第 \(i+8\) 个人肯定没打饭,所以 \(j>>1\) 后的第七位肯定是 0,\(j>>1\) 以后也符合,由于上一个打饭的是 \(i+k=i+1+k-1\),所以这样转移正确。

如果不是呢?那么我们就要选一个人来打饭,枚举下标从 \(t=0\sim 7\),即枚举 \(i\) 及其后的 7 个人,\(dp[i][j|(1<<t)][t+8]=dp[i][j][k+8]+w(i+k,i+t)\),这个比较好理解,但是我们肯定不能盲目枚举 \(t\),要考虑忍耐值,我们设 \(r\),为当前枚举 \(t\) 前面所有没有打饭的人忍耐范围,即所有人忍耐值加上自身下标的最小值,如果 \(t<=r\) 才能转移,否则退出,然后用当前 \(t\) 更新 \(r\),依次类推,最后答案就是 \(dp[n+1][0][-8 \sim -1]\)。

或许可以滚动数组?当然这里是刷表法(所有当前状态转移给未来状态),不是常写的填表法(未来状态从确定好的状态转移),所以有些不同。

点击查看代码
/*
Just Sayori
不过有的时候,当你在心中有一片小乌云时。。
一首悲伤的诗能给这片乌云一个小小的拥抱。。
然后它就会变成一道漂亮的开心彩虹!

毁黄金黎明,弑师弑友
艾华斯附身,为法之书
知世界大战,不管不顾
为幻想杀手,建立学都
为了自己的计划,失忆上条
同意 6 级害死妹妹上万
隐瞒里幻意欲灭口右火
无视只眼害死上条无数
派脑干袭击上里大输特输
无视大热浪放任元素
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
十亿可能性,袭击大英
解伦敦结界,破坏核心
使上条当麻,遭受折磨
信一方通行,传位理事
对学都不管不顾,安娜搞事
超绝者叫牢登,因月之子
发现魔法理论,出爱丽丝
爱丽丝使上条当麻死亡
想要帮忙,却被克鲁兹吓哭
想和当麻旅馆搞黄色
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
九三零送外挂送走队友数多
坑一方为学都打工还债十亿
送 aaa,电击御坂美琴
竟不知女儿被恶魔欺骗
美国洛杉矶,大闹会议现场
做个天赋异禀的暴徒
选了这条路习惯危机四伏
余生刀枪不入
*/
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 2005
#define M 0x3f3f3f3f3f3f3f3f
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
int a[N],b[N];
ll dp[2][1<<8][16];
int w(int x,int y)
{
	if (!x) return 0;//判初始情况
	return a[x]^a[y];
}
void work()
{
	int n=read(),now=1;
	ll ans=M;
	for (rnt i=1;i<=n;i++) a[i]=read(),b[i]=read();
	for (rnt j=0;j<1<<8;j++)
		for (rnt k=0;k<=15;k++)
			dp[0][j][k]=dp[1][j][k]=M;
	dp[0][0][7]=0;//这里的 7 是 -1 偏移值,1+(-1)=0
	for (rnt i=1;i<=n;i++)
	{
		now^=1;
		for (rnt j=0;j<1<<8;j++)
			for (rnt k=0;k<=15;k++)
				dp[now^1][j][k]=M;
		for (rnt j=0;j<1<<8;j++)
			for (rnt k=-8;k<=7;k++)//记得偏移
			{
				if (j&1)
				{
					if (k+7>=0) dp[now^1][j>>1][k+7]=min(dp[now^1][j>>1][k+7],dp[now][j][k+8]);
				}
				else
				{
					for (rnt t=0,r=n+1;t<=7;t++)
						if ((!(j>>t&1)))
						{
							if (i+t>r) break;
							dp[now][j|(1<<t)][t+8]=min(dp[now][j|(1<<t)][t+8],dp[now][j][k+8]+w(i+k,i+t));
							r=min(r,i+t+b[i+t]);
						}
				}
			}
	}
	for (rnt i=0;i<=7;i++) ans=min(ans,dp[now^1][0][i]);
	write(ans),pr(10);
}
int main()
{
	int t=read();
	while (t--) work();
	return 0;
}

ARC160F

神秘题。

一个 trick,用 \(n\) 个串表示,第 \(i\) 个串每一位表示这一位上的数是否大于等于 \(i\),那么对于这 \(n\) 个串,一个排列排好序当且仅当 \(n\) 个串全部从小到大排好序,低位全为 0,高位全为 1。那么如何求出这些排列呢?我们考虑对二进制串建图,对于二进制串 \(i,j\),\(i\) 向 \(j\) 连有向边当且仅当 \(j\) 的二进制位比 \(i\) 的多一个 1,那么从全 0 串走到全 1 串,经过的每个串都被排好序,这样的走法得到的排列即为所求,非常巧妙。

然后我们进行 \(dp\),从小到大枚举每个串(注意判断是否有序),向连了边的点转移(这里应该不需要真连边,只用枚举每个为 0 的二进制位即可)。\(dp[i+(1<<j)]+=dp[i],dp[0]=1\)。单次复杂度为 \(O(n2^n)\),总复杂度为 \(O(nm2^n)\),好了现在正确性有了,复杂度更劣了。

我们想到,不可能每次操作都会新增答案,我们发现操作可能会把一个逆序对消掉,而最后我们是一个逆序对都不要的。我们称能减少逆序对的操作为有效操作。那么最多有 \(O(n^2)\) 个有效操作,即整个序列的逆序对个数,只在意这些有效操作,复杂度能降至 \(O(2^nn^3)\)。

然后就是具体实现了,可以通过位运算处理出初始哪些二进制串是合法的,即高位一堆 1,低位一堆 0 的,如何得到一个二进制串经过操作之后合不合法呢?对于一个操作 \((x,y)(x<y)\),枚举所有二进制串,如果一个串 \(c_x=1,c_y=0\),那么就说明有逆序对,这个操作对它有影响,需要交换。我们保存每个二进制串经过操作之后的二进制串状态,这样就可以判合不合法了。然后我们把包含 \(x,y\) 的操作都设为无效操作,然后再一个一个更新,同样是判 \(c_i=1,c_j=0(i<j)\),判断是否是有效操作,这里不是复杂度瓶颈,所以可以暴力一点(主要是不这样会 WA,不知道为什么)。然后再 dp 转移就能得出当前的答案。记得开 long long。

点击查看代码
/*
Just Sayori
不过有的时候,当你在心中有一片小乌云时。。
一首悲伤的诗能给这片乌云一个小小的拥抱。。
然后它就会变成一道漂亮的开心彩虹!

毁黄金黎明,弑师弑友
艾华斯附身,为法之书
知世界大战,不管不顾
为幻想杀手,建立学都
为了自己的计划,失忆上条
同意 6 级害死妹妹上万
隐瞒里幻意欲灭口右火
无视只眼害死上条无数
派脑干袭击上里大输特输
无视大热浪放任元素
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
十亿可能性,袭击大英
解伦敦结界,破坏核心
使上条当麻,遭受折磨
信一方通行,传位理事
对学都不管不顾,安娜搞事
超绝者叫牢登,因月之子
发现魔法理论,出爱丽丝
爱丽丝使上条当麻死亡
想要帮忙,却被克鲁兹吓哭
想和当麻旅馆搞黄色
要多少篇幅才能书尽恶毒
要多儒雅才显的无辜
九三零送外挂送走队友数多
坑一方为学都打工还债十亿
送 aaa,电击御坂美琴
竟不知女儿被恶魔欺骗
美国洛杉矶,大闹会议现场
做个天赋异禀的暴徒
选了这条路习惯危机四伏
余生刀枪不入
*/
#pragma GCC diagnostic ignored "-Wnarrowing"
#pragma GCC diagnostic ignored "-Wsign-compare"
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>
#include <vector>
#include <random>
#include <climits>
#include <list>
#define ll long long
#define lll __int128_t
#define rnt register int
#define gr getchar
#define pr putchar
#define N 500005
#define M 1000000007
using namespace std;
inline ll read()
{
	ll x=0,f=1;
	char ch=gr();
	while (ch<'0' || ch>'9') ch=='-'?f=-1,ch=gr():ch=gr();
	while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gr();
	return x*f;
}
inline void write(ll x)
{
	int top=0,stack[39];
	if (x<0) pr('-'),x=-x;
	do stack[++top]=x%10,x/=10;
	while (x);
	while (top) pr(stack[top--]^48);
}
int f[1<<15];
ll dp[1<<15];
bitset<16> g[16];
bitset<1<<15> ok;
inline bool check(int s,int x,int y)//判断是否是 c_x=1 c_y=0
{
	return (((s&(1<<(min(x,y)-1)))) && (!(s&(1<<(max(x,y)-1)))));
}
int main()
{
	int n=read(),m=read();
	ll w=1;
	for (rnt i=0;i<=n;i++) ok[((1<<i)-1)<<(n-i)]=1;
	for (rnt i=0;i<1<<n;i++) f[i]=i;//二进制串 i 更改后的值 
	for (rnt i=1;i<=n;i++)//初始的所有操作都设为有效
		for (rnt j=1;j<=n;j++)
			g[i][j]=1;
	for (rnt i=1,a,b,c,d,x,y;i<=m;i++)
	{
		x=read(),y=read(),c=(x+w)%n+1,d=(y+w*2)%n+1,a=min(c,d),b=max(c,d);
		if (g[a][b])
		{
			for (rnt i=1;i<=n;i++) g[i][a]=g[a][i]=g[i][b]=g[b][i]=0;//暴力先全设为无效操作
			for (rnt i=0;i<1<<n;i++)
			{
				if (check(f[i],a,b))
					f[i]=f[i]^(1<<(a-1))^(1<<(b-1));
				for (rnt j=1;j<=n;j++)
				{
					if (check(f[i],j,a)) g[j][a]=g[a][j]=1;
					if (check(f[i],j,b)) g[j][b]=g[b][j]=1;
				}
			}
			for (rnt i=0;i<1<<n;i++) dp[i]=0;
			dp[0]=1;
			for (rnt i=1;i<1<<n;i++)
				if (ok[f[i]])
				{
					for (rnt j=1;j<=n;j++)//枚举某一位的 1
						if (i>>(j-1)&1)
							dp[i]+=dp[i^(1<<(j-1))];
				}
			w=dp[(1<<n)-1];
		}
		write(w),pr(10);
	}
	return 0;
}
posted @ 2026-11-04 10:03  某三年的无用名称  阅读(1)  评论(0)    收藏  举报