2021.9.25长乐模拟

2021.9.25长乐模拟

昨天模拟现在才写总结……
订正订了一整天

赛时:

早7:30到机房
今天模拟有预告,所以并不很紧张
昨晚还看了看关于换根dp的东西
拿到题就开始遍历
T 1 T1 T1……嗯?!!!做过原题!!!切了切了!!!
T 2 T2 T2……能想到的思路只有枚举 3 n 3^n 3n枚举每种状态,能过30分
T 3 T3 T3……只有暴力 n 3 n^3 n3的思路, n 2 n^2 n2做法好像可以dp,可以冲一下
T 4 T4 T4……暴搜 2 n 2^n 2n能过15吧
此时过去30min
然后按照gg的教诲能切的题应该放在最后
于是我先开了T3,目测代码难度最小
T 3 T3 T3 n 3 n^3 n3暴力秒出
然后一看数据范围惊恐得发现 n 3 n^3 n3好像一分没有……
最小的数据是 500 500 500 n 3 n^3 n3稳T
所以必须想出 n 2 n^2 n2的dp
然后发现修改当前位置对于之前的 c i c_{i} ci没有影响
于是就想到可以从后往前转移
然后时间就过了 1 h 1h 1h……
赶紧去开 T 4 T4 T4
T 4 T4 T4准备写搜索的时候发现他是同时转两只鞋
这使得代码难度直接上升了一个档次
又试着写了 1 h 1h 1h没写出来
只好去看 T 2 T2 T2
T 2 T2 T2也十分不好写
这个三进制状态枚举直接卡住了……
写了一会眼看只剩一个小时
赶紧去切 T 1 T1 T1
T 1 T1 T1倒是出奇顺利
30 m i n 30min 30min写完调完
剩下 30 m i n 30min 30min
又回头去搞T3
(T2,T4口胡了个乱搞就弃了)
T 3 T3 T3想出了 d p dp dp状态
f i , 0 / 1 f_{i,0/1} fi,0/1表示考虑到第i个数是是否用了修改机会
然后 O ( n ) O(n) O(n)枚举i然后再 O ( n ) O(n) O(n)枚举改成哪个数
总复杂度 O ( n 2 ) O(n^2) O(n2)
然后 f i , 0 = f i − 1 , 0 + i ∗ c i f_{i,0}=f_{i-1,0}+i*c_{i} fi,0=fi1,0+ici
f i , 1 = m i n ( f i − 1 , 0 + i ∗ c i − 1 + ∣ a i − a j ∣ , f i − 1 , 1 + i ∗ c i ) f_{i,1}=min(f_{i-1,0}+i*c_{i-1}+|a_{i}-a{j}|,f_{i-1,1}+i*c_{i}) fi,1=min(fi1,0+ici1+aiaj,fi1,1+ici)
然而转移时 c i c_{i} ci的修改没有处理好导致样例一直不过……
最后到时间就交了
期望得分: 100 + 0 + 0 + 0 = 100 100+0+0+0=100 100+0+0+0=100

赛后

实际得分: 30 + 0 + 0 + 0 = 30 30+0+0+0=30 30+0+0+0=30
??? T 1 T1 T1居然挂分???
打开代码对照之前 A C AC AC的代码发现我少了一句:
在这里插入图片描述
他妈的我多次使用单调队列没清空!!!
哎呀这70分呀!!!
加上就是 r n k 6 rnk6 rnk6了呀!!!(现在 r n k 13 rnk13 rnk13)

正解+订正:

T 1 : T1: T1:

预处理出对于每个 𝑖,以 𝑖 为结尾且不包含任何模式串的最长区间后,单调队列优化 DP
即可。
时间复杂度: O ( n m + ∑ i = 1 m ∣ T i ∣ ) O(nm+\sum_{i=1}^m|T_{i}|) O(nm+i=1mTi)

啥也8说了……多次调用不清空,爆零两行泪QAQ
预处理出对于每个 𝑖,以 𝑖 为结尾且不包含任何模式串的最长区间后,单调队列优化 DP
即可。
时间复杂度: O ( n m + ∑ i = 1 m ∣ T i ∣ ) O(nm+\sum_{i=1}^m|T_{i}|) O(nm+i=1mTi)

啥也8说了……多次调用不清空,爆零两行泪QAQ
代码:

#include<bits/stdc++.h>
#define ll long long
#define int ll
using namespace std;
const int maxn=2e5+5;
int n,m,q[maxn],nxt[maxn],pre[maxn],a[maxn],f[maxn];
char s[maxn],t[maxn]; 
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c<='9'&&c>='0')) ch=c,c=getchar();
	while(c<='9'&&c>='0') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
signed main()
{
//	freopen("wzadx.in","r",stdin);
//	freopen("wzadx.out","w",stdout);
	n=read(),m=read();
	scanf("%s",s+1);
	for(int i=1;i<=n;i++) a[i]=read();
	while(m--)
	{
		scanf("%s",t+1);
		int len=strlen(t+1);
		for(int i=2,j=0;i<=len;i++)
		{
			while(j&&t[i]!=t[j+1]) j=nxt[j];
			if(t[i]==t[j+1]) j++;
			nxt[i]=j; 
		}
		for(int i=1,j=0;i<=n;i++)
		{
			while(j&&s[i]!=t[j+1]) j=nxt[j];
			if(s[i]==t[j+1]) j++;
			if(j==len) pre[i]=max(pre[i],i-len+1),j=nxt[j]; 
		}
		int head=1,tail=1,lim=0;
		q[1]=0;//这一行7个字符一个值10分……QAQ
		for(int i=1;i<=n+1;i++)
		{
			while(head<=tail&&q[head]<lim) head++;
			f[i]=f[q[head]]+a[i];
			while(head<=tail&&f[q[tail]]>=f[i]) tail--;
			q[++tail]=i;
			lim=max(lim,pre[i]);
		}
	}
	printf("%lld",f[n+1]);
	return 0;
}
/*
5 3
abcde
3 1 3 1 3
abc
bcd
cde
*/

T 2 : T2: T2:

显然这是一个树的结构。对于操作 1 u v,令 𝑓𝑎𝑣 = 𝑢。
记 ℎ𝑢 表示原来在点 𝑢 上的动物能活到最后(就是一路杀到根节点,并且一直活在根节
点)的概率。
一开始所有的 ℎ𝑢 都是 1。
对于操作 2 u,𝑎𝑛𝑠 = ℎ𝑢 × 3^𝑛。
在 𝑢, 𝑣 还没连边的时候,𝑣 子树里的每个 ℎ𝑥 都要乘上 1 3 \dfrac 13 31,因为这些动物杀到 𝑣 之后,要跟 𝑢 里的动物 PK,赢的概率是 1 3 \dfrac 13 31
同理 𝑢 子树里每个 ℎ𝑥 都要乘上 2 3 \dfrac 23 32
线段树直接维护,时间复杂度 𝑂(𝑛 log 𝑛)。

好家伙我根本没往树的方向想……隔壁范佬看到u,v两个字母就想到了树形结构
想到树形结构就可以用并查集暴力过60……
然后线段树维护也可以参照树剖想到
然而我一个也没想到
代码:

#include<bits/stdc++.h>
#define ll long long
#define int ll
#define ls k<<1
#define rs k<<1|1
using namespace std;
const int maxn=2e5+5,mod=998244353;
int n,m,f[maxn][2],fa[maxn],tree[maxn<<2],laz[maxn<<2],dfn[maxn],cnt,inv,ans=1,siz[maxn];
vector<int>e[maxn];
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c<='9'&&c>='0')) ch=c,c=getchar();
	while(c<='9'&&c>='0') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
int qpow(int a,int b)
{
	int ret=1;
	while(b)
	{
		if(b&1) ret=(ret*a)%mod;
		a=(a*a)%mod;
		b>>=1;
	}
	return ret;
}
void dfs(int x)
{
	dfn[x]=++cnt;
	for(int i=0;i<(int)e[x].size();i++) dfs(e[x][i]);
	return;
}
void add(int k,int l,int r,int v)
{
	(laz[k]*=v)%=mod;
	(tree[k]*=v)%=mod;
}
void pushdown(int k,int l,int r)
{
	if(laz[k]==1) return;
	int mid=(l+r)>>1;
	add(ls,l,mid,laz[k]);add(rs,mid+1,r,laz[k]);
	laz[k]=1;
}
//void pushup(int k) {tree[k]=(tree[ls]*tree[rs])%mod;}
void build(int k,int l,int r)
{
	tree[k]=qpow(3,n);laz[k]=1;
	if(l==r) return;
	int mid=(l+r)>>1;
	build(ls,l,mid);build(rs,mid+1,r);
//	pushup(k);
}
void change(int k,int l,int r,int x,int y,int v)
{
//	if(l>y||x<r) return;
	if(x<=l&&r<=y) {add(k,l,r,v);return;}
	int mid=(l+r)>>1;
	pushdown(k,l,r);
	if(x<=mid) change(ls,l,mid,x,y,v);
	if(y>mid) change(rs,mid+1,r,x,y,v);
//	pushup(k);
}
int query(int k,int l,int r,int x)
{
	if(l==r) return tree[k];
	int mid=(l+r)>>1;
	pushdown(k,l,r);
	if(x<=mid) return query(ls,l,mid,x)%mod;
	else return query(rs,mid+1,r,x)%mod;
}
signed main()
{
	n=read(),m=read();
	for(int i=1;i<=m;i++)
	{
		int op=read();
		if(op==1)
		{
			f[i][0]=read(),f[i][1]=read();
			fa[f[i][1]]=f[i][0];
			e[f[i][0]].push_back(f[i][1]);
		}
		else f[i][0]=read();
	}
	for(int i=1;i<=n;i++)
	{
		siz[i]=1;
		if(!fa[i]) dfs(i);
	}
//	for(int i=1;i<=n;i++) cout<<dfn[i]<<" ";
	build(1,1,n);
	inv=qpow(3,mod-2),ans=qpow(3,n);
//	cout<<inv<<endl;
	for(int i=1;i<=m;i++)
	{
		if(!f[i][1]) printf("%lld\n",query(1,1,n,dfn[f[i][0]])%mod);
		else
		{
//			for(int i=dfn[f[i][0]];i<=dfn[f[i][0]]+siz[f[i][0]]-1;i++) cout<<query(1,1,n,i)%mod<<" ";
			change(1,1,n,dfn[f[i][0]],dfn[f[i][0]]+siz[f[i][0]]-1,inv*2%mod);
			change(1,1,n,dfn[f[i][1]],dfn[f[i][1]]+siz[f[i][1]]-1,inv);
			siz[f[i][0]]+=siz[f[i][1]];
		}
	}
	return 0;
}
/*
3 5
2 1
1 2 1
2 1
1 2 3
2 1
*/

T 3 : T3: T3:

显然若将 𝑎𝑖 修改为某个未在序列中出现的数字,𝑐1, 𝑐2, … , 𝑐𝑖−1 不变,𝑐𝑖, 𝑐𝑖+1, … , 𝑐𝑛 增大,答案一定更劣。
于是我们设修改操作是将 𝑎𝑥 修改为 𝑎𝑦,其中 1 ≤ 𝑥, 𝑦 ≤ 𝑛,由于每个数字只有在第一次出现时会对 𝑐𝑖 产生影响,所以我们约定 𝑥, 𝑦 分别是 𝑎𝑥, 𝑎𝑦 的第一次出现。
设 𝑧 为 𝑎𝑥 下一次出现的位置,特别地,若 𝑎𝑥 只出现了一次,则 𝑧 = 𝑛 + 1。
接下来我们对 𝑥, 𝑦 进行讨论(下设 𝑆𝑖 = ∑ j = i n \sum_{j=i}^n j=in𝑛𝑗=𝑖 ):
若 𝑦 < 𝑥,则修改会导致 𝑐[𝑥,𝑧) 减小 1,因此新增的代价为 |𝑎𝑥 − 𝑎𝑦| − 𝑆𝑥 + 𝑆𝑧,此时除𝑎𝑦 外所有的信息只与 𝑥 有关,于是可在枚举 𝑥 之后,用 set 维护所有合法的 𝑎𝑦,选择与 𝑎𝑥差值最小的进行转移即可。
若 𝑦 > 𝑥:
若 𝑦 < 𝑧,则修改将导致 𝑐[𝑦,𝑧) 减小 1,新增代价为 |𝑎𝑥 − 𝑎𝑦| − 𝑆𝑦 + 𝑆𝑧。
若 𝑦 > 𝑧,则修改将导致 𝑐[𝑧,𝑦) 增加 1,没有意义。
接下来对 𝑎𝑥 和 𝑎𝑦 的大小进行讨论:
若 𝑎𝑦 < 𝑎𝑥,则新增代价为 (𝑆𝑧 + 𝑎𝑥) − (𝑆𝑦 + 𝑎𝑦)。
若 𝑎𝑦 > 𝑎𝑥,则新增代价为 (𝑆𝑧 − 𝑎𝑥) − (𝑆𝑦 − 𝑎𝑦)。
用两个树状数组进行维护即可。
时间复杂度 𝑂(𝑛 log 𝑛)。

考场想到了y<x的情况,没有接着往下讨论……
但是用树状数组和set维护属实想不到
代码:

#include<bits/stdc++.h>
#define ll long long
#define int ll
using namespace std;
const int maxn=5e5+5;
int n,m,a[maxn],tree1[maxn],tree2[maxn],b[maxn],nxt[maxn],sum[maxn],ans,del;
bool vis[maxn];
set<int>s;
map<int,int>lst;
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c<='9'&&c>='0')) ch=c,c=getchar();
	while(c<='9'&&c>='0') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
int lowbit(int x) {return x&(-x);}
void add1(int x,int v)
{
	for(int i=x;i<=n;i+=lowbit(i)) tree1[i]=max(tree1[i],v);
}
void add2(int x,int v)
{
	for(int i=x;i<=n;i+=lowbit(i)) tree2[i]=max(tree2[i],v);
}
int query1(int x)
{
	int ret=-0x3f3f3f3f;
	for(int i=x;i;i-=lowbit(i)) ret=max(ret,tree1[i]);
	return ret;
}
int query2(int x)
{
	int ret=-0x3f3f3f3f;
	for(int i=x;i;i-=lowbit(i)) ret=max(ret,tree2[i]);
	return ret;
}
signed main()
{
	memset(tree1,-0x3f,sizeof(tree1));
	memset(tree2,-0x3f,sizeof(tree2));
	n=read();
	for(int i=n;i>=0;i--) sum[i]=sum[i+1]+i;
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		if(!lst[a[i]]) ans+=sum[i],b[++m]=a[i],vis[i]=1;
		else nxt[lst[a[i]]]=i;
		lst[a[i]]=i;
		nxt[i]=n+1;
	}
	sort(b+1,b+m+1);
	s.insert(0x3f3f3f3f),s.insert(-0x3f3f3f3f);
	for(int i=1;i<=n;i++)
	{
		if(vis[i])
		{
			set<int>::iterator it=s.lower_bound(a[i]);
			del=min(del,-sum[i]+sum[nxt[i]]+*it-a[i]);
			del=min(del,-sum[i]+sum[nxt[i]]+a[i]-*(--it));
			s.insert(a[i]);
		}
	}
	for(int i=n;i>=0;i--)
	{
		if(vis[i])
		{
			int pos=lower_bound(b+1,b+m+1,a[i])-b;
			del=min(del,sum[nxt[i]]+a[i]-query1(pos));
			del=min(del,sum[nxt[i]]-a[i]-query2(n-pos+1));
			add1(pos,sum[i]+a[i]);add2(n-pos+1,sum[i]-a[i]);
		}
	}
	printf("%lld",ans+del);
	return 0;
}

T 4 : T4: T4:

这个旋转操作看上去很乱,于是我们考虑旋转操作的本质是是什么。
考虑把鞋子分别朝右、下、左、上记作权值 0,1,2,3。
那么我们就能得出这个旋转操作的本质:任何时候保证所有鞋子的权值之和在模4意义下的结果不变。
回到原问题,题目要求匹配,故先对相邻的异脚鞋连边之后求一遍二分图最大匹配。
如果原图不存在完美匹配,那么由于我们只需让所有鞋子的权值之和在模 4 意义下的结果不变,所以这时最大匹配就是答案。
否则由于所有鞋子权值和的限制,答案可能会比最大匹配少 。
考虑如果一组匹配方案给定,所有鞋子的权值之和模4要怎么算。易得:
(1)如果一组匹配不横跨两行,那么这组匹配对模4意义下的权值和不贡献。
(2)如果一组匹配横跨两行,那么这组匹配对模4意义下的权值和贡献 。
于是我们只需知道这组匹配方案中横跨两行的匹配数的奇偶性。
而实际上,规模给定的网格图的任意完美匹配中,跨行的匹配边数的奇偶性都相同。
故原图所有完美匹配方案下,所有鞋子的权值之和在模 4 意义下全相同。判断其是否与
初始状态相同即可。

二分图匹配属实没想到……
还以为是分治
话说二分图最大匹配不是noip内容吧
这下学到了,以后这种配对题可以考虑二分图匹配
代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=105;
const int dis[4][2]={{0,-1},{0,1},{-1,0},{1,0}};
int n,m,sum,head[maxn*maxn],ecnt,match[maxn*maxn],vis[maxn*maxn];
bool vis1[maxn*maxn];
char a[maxn][maxn];
vector<int>v1,v2;
struct edge
{
	int nxt,to;
}e[maxn*maxn*4];
inline ll read()
{
	ll ret=0;char ch=' ',c=getchar();
	while(!(c<='9'&&c>='0')) ch=c,c=getchar();
	while(c<='9'&&c>='0') ret=(ret<<1)+(ret<<3)+c-'0',c=getchar();
	return ch=='-'?-ret:ret;
}
void add(int x,int y)
{
//	printf(" add(%d,%d)\n",x,y);
	e[++ecnt]=(edge){head[x],y};
	head[x]=ecnt;
}
int getpos(int i,int j) {return (i-1)*m+j;}
bool hungary(int u,int t)
{
	for(int i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to;
		if(vis[v]!=t)
		{
			vis[v]=t;
			if(!match[v]||hungary(match[v],t))
			{
				match[v]=u;
				return 1;
			}
		}
	}
	return 0;
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
	for(int i=1;i<=n;i++)
	{
		char ch[maxn];scanf("%s",ch+1);
		for(int j=1;j<=m;j++)
		{
			(sum+=ch[j]=='R'?0:ch[j]=='D'?1:ch[j]=='L'?2:3)%=4;
		}
	}
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if((i+j)&1)
			{
				for(int k=0;k<4;k++)
				{
					int xx=i+dis[k][0],yy=j+dis[k][1];
					if(xx<1||xx>n||yy<1||yy>m) continue;
					if(a[i][j]!=a[xx][yy])
					{
						add(getpos(i,j),getpos(xx,yy));
						v1.push_back(getpos(i,j));
						v2.push_back(getpos(xx,yy));
					}
				}
			}
		}
	}
	int ans=0;
	for(int i=0;i<(int)v1.size();i++)
	{
		if(!vis1[v1[i]])
		{
			vis1[v1[i]]=1;
			if(hungary(v1[i],v1[i])) ans++;	
		}
	}
//	cout<<ans<<endl;
	if(ans*2!=n*m) {printf("%d",ans);return 0;}
//	cout<<ans<<endl;
	int tot=0;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			if(!((i+j)&1))
			{
				if((match[getpos(i,j)]-1)/m+1==i) (tot+=2)%=4;
			}
		}
	}
	if(tot!=sum) ans--;
	printf("%d",ans);
	return 0;
}
/*
2 2
RL
LR
UR
LU
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值