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;
}

image

有图有真相! \(l=1,r=20\) 时构建出来的森林大概长成这个样子,加粗的是最优解选择的点:

image

然而并不需要树上 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

image

本题中要使用莫队需要实现三个操作:

  • \(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;
}
posted @ 2024-11-18 21:21  Jerrycyx  阅读(6)  评论(0编辑  收藏  举报