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=fi−1,0+i∗ci
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(fi−1,0+i∗ci−1+∣ai−aj∣,fi−1,1+i∗ci);
然而转移时
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=1m∣Ti∣)
啥也8说了……多次调用不清空,爆零两行泪QAQ
预处理出对于每个 𝑖,以 𝑖 为结尾且不包含任何模式串的最长区间后,单调队列优化 DP
即可。
时间复杂度:
O
(
n
m
+
∑
i
=
1
m
∣
T
i
∣
)
O(nm+\sum_{i=1}^m|T_{i}|)
O(nm+∑i=1m∣Ti∣)
啥也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
*/