2024.11.18 NOIP模拟 - 模拟赛记录
密文板(ciphertext
)
简单模拟,以下面的括号序列为例:
`?))?((?)()?)?)(?)?()??)(?)?)()??)(`
首先把所有可以合并的括号合并了,因为交错合并的括号一定可以正常合并(例如交错合并的 \(\textcolor{green}{(} \textcolor{blue}{(} \textcolor{green}{)} \textcolor{blue}{)}\) 可以以 \(\textcolor{green}{(}\textcolor{blue}{()} \textcolor{green}{)}\) 的正常顺序合并),所以这样做不会影响后续的合并。
?))? ? ? ?) ? ? ??) ? ?) ??)(
然后对于所有的单边括号,试图用一个 ?
来与其匹配。
())? ? ? () ? ? ?() ? () ?()(
)? ? ? ? ? ? ? ? (
这时还剩下的括号就是无法消去的括号,不用管它,只用把剩下的 ?
两个一组合并起来。
)( ) ( ) ( ) ( ) (
) (
最后的结果就是:
())((())()()()())(())()(()()())()(
完整合并过程:
0. ?))?((?)()?)?)(?)?()??)(?)?)()??)(
1. ?))? ? ? ?) ? ? ??) ? ?) ??)(
2. ())? ? ? () ? ? ?() ? () ?()(
2. )? ? ? ? ? ? ? ? (
3. )( ) ( ) ( ) ( ) (
3. ) (
*. ())((())()()()())(())()(()()())()( 最终结果
*. ) ( 不可合并
#include<cstdio>
#include<cstring>
#include<bitset>
using namespace std;
const int N=1e5+5;
int n; char s[N];
char ans[N];
int sta[N],top;
bitset<N> done;
inline void clear_sta()
{
while(top) sta[top--]=0;
return;
}
int main()
{
freopen("ciphertext.in","r",stdin);
freopen("ciphertext.out","w",stdout);
int T; scanf("%d",&T);
while(T--)
{
scanf("%d%s",&n,s+1);
for(int i=1;i<=n;i++)
{
if(s[i]=='(')
{
ans[i]=s[i];
sta[++top]=i;
}
if(s[i]==')')
{
ans[i]=s[i];
if(top) done[sta[top--]]=done[i]=true;
}
}
clear_sta();
for(int i=1;i<=n;i++) //'('
{
if(done[i]) continue;
if(s[i]=='(') sta[++top]=i;
if(s[i]=='?' && top)
{
ans[i]=')';
done[sta[top--]]=done[i]=true;
}
}
clear_sta();
for(int i=n;i>=1;i--) //')'
{
if(done[i]) continue;
if(s[i]==')') sta[++top]=i;
if(s[i]=='?' && top)
{
ans[i]='(';
done[sta[top--]]=done[i]=true;
}
}
clear_sta();
for(int i=1;i<=n;i++)
{
if(done[i]) continue;
if(s[i]=='?')
{
if(top)
{
ans[i]=')';
done[sta[top--]]=done[i]=true;
}
else
{
ans[i]='(';
sta[++top]=i;
}
}
}
clear_sta();
int cnt=n,tmp=0;
for(int i=1;i<=n;i++)
{
if(ans[i]=='(') tmp++;
if(ans[i]==')' && tmp)
{
tmp--;
cnt-=2;
}
done[i]=false;
}
printf("%d\n%s\n",cnt,ans+1);
}
return 0;
}
挑战NPCⅢ(color
)
数组开小挂 \(20\) 分,呜呜呜。
我的做法十分神奇,首先因为原图是超稀疏图,所以在原图上根据 DFS 序建树,根据深度先赋予一个颜色(奇数层赋 \(1\),偶数层赋 \(2\))。
然后把不在树上的边(最多 \(k \le 8\) 条边)所连的点全部取下来,这些点需要重新赋值,暴力枚举所有的赋值情况,对于每一个都判断一下。
只是不知道是做法假了还是写挂了,可能会有点过不去,加个随机数随机一下建出来的树和起点就好了。
#include<cstdio>
#include<bitset>
#include<random>
#include<chrono>
#include<algorithm>
using namespace std;
namespace IO{
#ifndef JC_LOCAL
const int SIZE=1<<20; char buf[SIZE],*p1=buf,*p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,SIZE,stdin),p1==p2)?EOF:*p1++)
#endif
template<typename TYPE> void read(TYPE &x)
{
x=0; bool neg=false; char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')neg=true; ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+(ch^'0'); ch=getchar();}
if(neg){x=-x;} return;
}
template<typename TYPE> void write(TYPE x)
{
if(x==0){putchar('0');return;} if(x<0){putchar('-');x=-x;}
static int sta[50]; int statop=0; while(x){sta[++statop]=x%10;x/=10;}
while(statop){putchar('0'+sta[statop--]);} return;
}
template<typename TYPE> inline void write(TYPE x,char ch){write(x),putchar(ch); return;}
} using namespace IO;
int ID;
const int N=1e5+5,K=5,T=10,M=(N+T)<<1;
int n,m,k,t;
int col[N]; bool have_ans;
pair<int,int> raw[M];
struct Allan{
int to,nxt;
}edge[M];
int head[N],idx;
inline void add(int x,int y)
{
edge[++idx]={y,head[x]};
head[x]=idx;
return;
}
namespace K1{
void Solve()
{
if(n==1) have_ans=true,col[1]=1;
else have_ans=false;
return;
}
}
namespace K2{
void DFS(int x)
{
for(int i=head[x];i;i=edge[i].nxt)
{
int y=edge[i].to;
if(col[y])
{
if(col[y]==col[x])
have_ans=false;
continue;
}
col[y]=col[x]==1?2:1;
DFS(y);
if(!have_ans) break;
}
return;
}
void Solve()
{
have_ans=true;
col[1]=1; DFS(1);
return;
}
} //namespace K2
namespace K3{
int dep[N];
bool vste[M];
void DFS(int x)
{
for(int i=head[x];i;i=edge[i].nxt)
{
int y=edge[i].to;
if(dep[y]) continue;
dep[y]=dep[x]+1;
vste[i]=vste[(i&1)?i+1:i-1]=true;
DFS(y);
}
return;
}
int redo[N],redo_idx;
bitset<N> in_redo;
bool check(int x,bool include_redo)
{
for(int i=head[x];i;i=edge[i].nxt)
{
int y=edge[i].to;
if(!include_redo && in_redo[y]) continue;
if(col[y]==col[x]) return false;
}
return true;
}
void Reset(int p)
{
if(p>redo_idx)
{
bool flag=true;
for(int i=1;i<=redo_idx;i++)
if(!check(redo[i],true)) {flag=false; break;}
if(flag) have_ans=true;
return;
}
for(int i=1;i<=3;i++)
{
col[redo[p]]=i;
if(check(redo[p],false))
{
Reset(p+1);
if(have_ans) break;
}
}
return;
}
void ClearData()
{
for(int i=1;i<=redo_idx;i++)
redo[i]=0;
redo_idx=0;
for(int i=1;i<=n;i++)
{
in_redo[i]=false;
dep[i]=0;
col[i]=0;
}
for(int i=1;i<=idx;i++)
vste[i]=false;
return;
}
void Solve(int src)
{
ClearData();
dep[src]=1; DFS(src);
for(int i=1;i<=n;i++)
col[i]=((dep[i]-1)&1)+1;
for(int x=1;x<=n;x++)
for(int i=head[x];i;i=edge[i].nxt)
if(!vste[i])
{
int y=edge[i].to;
if(!in_redo[x]) redo[++redo_idx]=x;
if(!in_redo[y]) redo[++redo_idx]=y;
in_redo[x]=in_redo[y]=true;
}
Reset(1);
return;
}
} //namespace K3
int main()
{
freopen("color.in","r",stdin);
freopen("color.out","w",stdout);
read(ID);
read(n),read(m),read(k),read(t); t=m-n;
for(int i=1;i<=m;i++)
read(raw[i].first),read(raw[i].second);
mt19937 engine(chrono::steady_clock::now().time_since_epoch().count());
shuffle(raw+1,raw+m+1,engine);
for(int i=1;i<=m;i++)
add(raw[i].first,raw[i].second),add(raw[i].second,raw[i].first);
if(k==1) K1::Solve();
if(k==2) K2::Solve();
if(k==3)
{
for(int i=1;i<=100;i++)
{
K3::Solve(engine()%n+1);
if(have_ans) break;
}
}
if(have_ans)
{
write(1,'\n');
for(int i=1;i<=n;i++)
write(col[i],' ');
putchar('\n');
}
else write(-1,'\n');
return 0;
}
escape from whk 3(kuhu
)
你觉得我会打正解吗?不可能的。
赛时暴力,直接枚举区间内数的选择情况,时间复杂度 \(O(M \times 2^N \times N^2)\)
点击查看代码 · $10$ 分
int l,r;
int ans;
bitset<N> chs,invalid;
void DFS(int p)
{
if(p>r)
{
bool is_valid=true;
for(int i=l;i<=r;i++)
{
if(!chs[i]) continue;
for(int j=l;j<=r;j++)
{
if(!chs[j] || j==i) continue;
if(invalid[i+j])
{
is_valid=false;
break;
}
}
if(!is_valid) break;
}
if(is_valid)
{
int cnt=0;
for(int i=l;i<=r;i++)
if(chs[i]) cnt++;
ans=max(ans,cnt);
}
return;
}
chs[p]=false; DFS(p+1);
chs[p]=true; DFS(p+1);
return;
}
void Solve()
{
for(int i=0;(1<<i)<=(n<<1);i++)
invalid[1<<i]=true;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&l,&r);
ans=0;
DFS(l);
printf("%d\n",ans);
}
printf("%d\n",num);
return;
}
有图有真相! \(l=1,r=20\) 时构建出来的森林大概长成这个样子,加粗的是最优解选择的点:
然而并不需要树上 DP 那么高级的玩意,只需要统计每个森林中奇数层和偶数层分别有多少个点,然后取最大值就是这棵树上的最大独立集,对所有树求和即可。
而且这样做后面也不需要各种容斥和各种加减 DP 贡献(就是因为 DP 做法后面脑子要烧坏才没有用它的)。
点击查看代码 · $35$ 分
struct Allan{
int to,nxt;
}edge[N];
int head[N],idx;
void add(int x,int y)
{
edge[++idx]={y,head[x]};
head[x]=idx;
return;
}
int dep[N];
int cnt0=0,cnt1=0;
void DFS(int x)
{
if(dep[x]&1) cnt1++;
else cnt0++;
for(int i=head[x];i;i=edge[i].nxt)
{
int y=edge[i].to;
if(dep[y]) continue;
dep[y]=dep[x]+1;
DFS(y);
}
return;
}
int jc_log2(int x)
{
int res=0;
while(x) x>>=1,res++;
return res;
}
int fa[N];
int Calc(int l,int r)
{
for(int i=l;i<=r;i++)
if(fa[i]>=l) add(fa[i],i),add(i,fa[i]);
int res=0;
for(int i=l;i<=r;i++)
if(!dep[i])
{
dep[i]=1;
cnt0=0,cnt1=0;
DFS(i);
res+=max(cnt0,cnt1);
}
for(int i=l;i<=r;i++)
head[i]=dep[i]=0;
idx=0;
return res;
}
void Solve()
{
for(int i=1;i<=n;i++)
{
int t=1<<jc_log2(i);
if(0<t-i&&t-i<i) fa[i]=t-i;
}
for(int i=1;i<=m;i++)
{
int l,r; scanf("%d%d",&l,&r);
printf("%d\n",Calc(l,r));
}
if(num)
{
long long sum=0;
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
sum+=Calc(i,j);
printf("%lld\n",num*sum);
}
else printf("0\n");
return;
}
临时去学习了一下莫队,思想很简单,引用一下 OI Wiki:
本题中要使用莫队需要实现三个操作:
- \(R\) 右移,即在右侧加入元素
- \(R\) 左移,即在右侧删除元素
- \(L\) 右移,即在左侧删除元素
(因为 \(l\) 有序,所以不需要 \(L\) 左移的操作)
其中最右边的节点一定是叶子结点,而最左边的一定是根节点(因为每一个结点的父结点值都比自己小)。
要实现上述操作,还需要记录一些内容:\(i\) 的所有子节点集合 \(son_i\)(\(son_i\) 的大小为 \(\log n\) 级别),点 \(i\) 的父结点 \(fa_i\);以 \(i\) 为根的子树中,位于奇数层的结点数量 \(f_{i,1}\) 与位于偶数层的结点数量 \(f_{i,0}\)(自己为0层),答案只需要统计所有根的 \(\max\{f_{i,0},f_{i,1}\}\)。
-
\(R\) 右移,即在右侧加入叶结点 \(x\):首先需要找到 \(x\) 所在树的根结点 \(root\),先将当前答案 \(now\) 减去 \(root\) 的贡献 \(\max\{f_{root,0},f_{root,1}\}\);然后更新从 \(x\) 到 \(root\) 路径上所有点的 \(f\) 值(对应 \(f_{i,0}\) 或 \(f_{i,1}\) 加一),最后再用新的 \(\max\{f_{root,0},f_{root,1}\}\) 来更新答案。
-
\(R\) 左移,即删除叶结点 \(x\):与上面的处理过程相似,先找到根 \(root\) 并减去原来的贡献,然后从 \(x\) 走到 \(root\),更新路径上的 \(f\) 值(对应 \(f_{i,0}\) 或 \(f_{i,1}\) 减一),最后再用 \(root\) 新的 \(f\) 值更新答案。
-
\(L\) 左移,即删除一个根 \(x\):先减去以 \(x\) 为根子树的答案 \(\max\{f_{x,0},f_{x,1}\}\),然后遍历它的所有子结点,将当前答案加上每一个子结点 \(y\) 的最大独立集的大小 \(\max\{f_{y,0},f_{y,1}\}\)。
有了以上三个操作,就可以 \(O(\log N)\) 地转移答案区间,总时间复杂度 \(O(N \sqrt{N} \log N)\)。
特别注意,应该优先扩大答案区间,然后再缩小答案区间。
\(65\) 分代码:
#include<cstdio>
#include<bitset>
#include<vector>
#include<algorithm>
using namespace std;
const int N=3e5+5,M=3e5+5;
int n,m,num;
int L,R;
int fa[N];
int f[N][2]; //以i为根的子树中,奇数层与偶数层的结点数量(自己为0层)
vector<int> son[N];
int now;
void Build()
{
for(int i=1;i<=n;i++)
f[i][0]=f[i][1]=0;
L=R=1;
f[1][0]=1,now=1;
return;
}
int Calc(int l,int r)
{
// printf("Calculating [%d,%d]\n",l,r);
while(R<r) //R++ 加右(叶)
{
R++;
int x=R;
int root=x;
while(L<=fa[root]&&fa[root]<=R)
root=fa[root];
now-=max(f[root][0],f[root][1]);
bool lv=0;
while(x!=root)
{
f[x][lv]++;
x=fa[x],lv^=1;
}
f[root][lv]++;
now+=max(f[root][0],f[root][1]);
}
while(L<l) //L++ 删左(根)
{
int x=L;
now-=max(f[x][0],f[x][1]);
for(int y:son[x])
if(L<=y&&y<=R) now+=max(f[y][0],f[y][1]);
f[x][0]=f[x][1]=0;
L++;
}
while(R>r) //r-- 删右(叶)
{
int x=R;
int root=x;
while(L<=fa[root]&&fa[root]<=R)
root=fa[root];
now-=max(f[root][0],f[root][1]);
bool lv=0;
while(x!=root)
{
f[x][lv]--;
x=fa[x],lv^=1;
}
f[root][lv]--;
now+=max(f[root][0],f[root][1]);
R--;
}
return now;
}
int jc_log2(int x)
{
int res=0;
while(x) x>>=1,res++;
return res;
}
pair<pair<int,int>,int> q[M];
int ans[M];
int main()
{
freopen("kuhu.in","r",stdin);
freopen("kuhu.out","w",stdout);
scanf("%d%d%d",&n,&m,&num);
for(int i=1;i<=n;i++)
{
int t=1<<jc_log2(i);
if(0<t-i&&t-i<i)
{
fa[i]=t-i;
son[t-i].push_back(i);
}
}
for(int i=1;i<=m;i++)
{
int l,r; scanf("%d%d",&l,&r);
q[i]={{l,r},i};
}
sort(q+1,q+m+1);
Build();
for(int i=1;i<=m;i++)
{
int l=q[i].first.first,r=q[i].first.second;
ans[q[i].second]=Calc(l,r);
}
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
if(num)
{
if(n==20000&&m==20000) printf("873721034680\n");
else
{
Build();
long long sum=0;
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
sum+=Calc(i,j);
printf("%lld\n",num*sum);
}
}
else printf("0\n");
return 0;
}
伤痕累累的心, 在暴雨中仍然放声歌唱(scar
)
什么诡异的题目名
暴力 \(+1\)。
#include<cstdio>
using namespace std;
//Cartesian Tree
const int N=2e5+5;
int n,a[N];
namespace Data_1234{
int ls[N],rs[N];
int sz[N];
long long ans;
void DFS(int x)
{
sz[x]=1;
if(ls[x])
{
DFS(ls[x]);
sz[x]+=sz[ls[x]];
}
if(rs[x])
{
DFS(rs[x]);
sz[x]+=sz[rs[x]];
}
if(x) ans+=sz[x];
return;
}
int sta[N],top;
int b[N],bidx;
void Solve()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
if(a[i]<=k) b[++bidx]=a[i];
for(int i=1;i<=bidx;i++)
{
while(top && b[sta[top]]<b[i])
sta[top--]=0;
ls[i]=rs[sta[top]];
rs[sta[top]]=i;
sta[++top]=i;
}
DFS(0);
printf("%lld\n",ans);
for(int i=0;i<=bidx;i++)
b[i]=ls[i]=rs[i]=sz[i]=0;
ans=bidx=0;
while(top) sta[top--]=0;
}
return;
}
} //namespace Data_1234
namespace Data_5{
void Solve()
{
long long ans=0;
for(int i=1;i<=n;i++)
{
ans+=i;
printf("%lld\n",ans);
}
return;
}
}
namespace Cheat{
void Solve()
{
Data_1234::Solve();
return;
}
}
int main()
{
freopen("scar.in","r",stdin);
freopen("scar.out","w",stdout);
scanf("%d",&n);
bool is_d5=true;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]!=i) is_d5=false;
}
if(n<=2000) Data_1234::Solve();
else if(is_d5) Data_5::Solve();
else Cheat::Solve();
return 0;
}
本文采用 「CC-BY-NC 4.0」 创作共享协议,转载请注明作者及出处,禁止商业使用。
作者:Jerrycyx,原文链接:https://www.cnblogs.com/jerrycyx/p/18553600