2022.9.29

一测:\(270pts\),原因 \(YES,NO\)(懂得都懂)

T1:P1230 智力大冲浪(黄)

T2:P3998 [SHOI2013]发微博(蓝)

T3:P3988 [SHOI2013]发牌(紫)

T4:P3989 [SHOI2013]阶乘字符串(紫)

T1:

按照扣款数排序,每次选择时间从大到小选,找到第一个没有使用过的时间点,标记即可。

时间复杂度:\(O(n^2)\)

\(O(n)\) 做法详见这里

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=505;
int n,m,vis[N];
struct node
{
	int name,data;
}a[N];
int cmp(node fi,node se)
{
	return fi.data>se.data;
}
int main()
{
	freopen("riddle.in","r",stdin);
	freopen("riddle.out","w",stdout);
	scanf("%d%d",&m,&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i].name);
	for(int i=1;i<=n;i++)scanf("%d",&a[i].data);
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=n;i++)
	{
		bool flag=1;
		for(int j=a[i].name;j>0;j--)
		{
			if(!vis[j])
			{
				vis[j]=1;
				flag=0;
				break;
			}
		}
		m-=flag*a[i].data;
	}
	printf("%d",m);
	return 0;
}

T2:

设数组 \(a_i\) 为第 \(i\) 人发消息数量,\(ans_i\) 为收到信息数。

对于 \(!\) 操作,操作 \(a_i=a_i+1\)

对于 \(+\) 操作,建边,并对两点 \((u,v)\) 进行操作:

\(ans_u=ans_u-a_v\)

\(ans_v=ans_v-a_u\)

对于 \(-\) 操作,标记两边删除的点,然后操作:

\(ans_u=ans_u+a_v\)

\(ans_v=ans_v+a_u\)

最后对建了边的点加上 \(a_i\)(建了不止一次的要重复加),删了边的减去 \(a_i\)(删了不止一次的要重复减)即可。

证明:

对于一个点,加入了另一个点后,前面的消息无法收到,所以减去前面消息,如果要删除,那么后面的消息无法收到,那么可以先加上对应点的消息数,最后处理时删除与建边同时加减会把多余的消息处理掉。(可以模拟一下)

#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
const int N=2e5+5;
vector<int>add[N],del[N];
int n,m,a[N],b[N];
int main()
{
	freopen("sina.in","r",stdin);
	freopen("sina.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		char opt;
		scanf("%c",&opt);
		while(opt!='!'&&opt!='-'&&opt!='+')scanf("%c",&opt);
		if(opt=='!')
		{
			int x;
			scanf("%d",&x);
			a[x]++;
		}
		if(opt=='-')
		{
			int u,v;
			scanf("%d%d",&u,&v);
			del[u].push_back(v);
			del[v].push_back(u);
			b[u]+=a[v];
			b[v]+=a[u];
		}
		if(opt=='+')
		{
			int u,v;
			scanf("%d%d",&u,&v);
			add[u].push_back(v);
			add[v].push_back(u);
			b[u]-=a[v];
			b[v]-=a[u];
		}
	}
	for(int i=1;i<=n;i++)
	{
		int len=add[i].size();
		for(int j=0;j<len;j++)b[add[i][j]]+=a[i];
		len=del[i].size();
		for(int j=0;j<len;j++)b[del[i][j]]-=a[i];
	}
	for(int i=1;i<=n;i++)printf("%d ",b[i]);
	return 0;
}

T3:

Splay 板子题。

先往后跑 \(r_i\) 步,同时取模,设跑到的位置为 \(k\),那求第 \(k\) 小,再把此点删了即可。

建树插入复杂度很高,Splay好像没有一个专门的建树的方法,那就可以用分治建树,从区间 \(1\) 开始 \(n\),每次取 \(mid\) 作为此点,然后建 \(l,mid-1\) 为左子树,\(mid+1,r\) 为右子树即可。

其他的平衡树也可以。

#include<iostream>
#include<cstdio>
using namespace std;
const int N=7e5+5;
int n,root,cnt;
struct node
{
	int fa,ch[2],siz,cnt,val;
}t[N];
inline void newnode(int &x,int fa,int val)
{
	x=++cnt;
	t[x].fa=fa;
	t[x].val=val;
	t[x].cnt=t[x].siz=1;
}
inline void connect(int x,int fa,int son)
{
	t[x].fa=fa;
	t[fa].ch[son]=x;
}
inline bool ident(int x,int fa)
{
	return t[fa].ch[1]==x;
}
inline void pushup(int x)
{
	t[x].siz=t[x].cnt+t[t[x].ch[0]].siz+t[t[x].ch[1]].siz;
}
inline void rotate(int x)
{
	int fa=t[x].fa,ff=t[fa].fa,k=ident(x,fa);
	connect(t[x].ch[k^1],fa,k);
	connect(fa,x,k^1);
	connect(x,ff,ident(fa,ff));
	pushup(fa),pushup(x);
}
void splay(int x,int topp=0)
{
	if(topp==0)root=x;
	while(t[x].fa!=topp)
	{
		int fa=t[x].fa,ff=t[fa].fa;
		if(ff!=topp)ident(x,fa)^ident(fa,ff)?rotate(x):rotate(fa);
		rotate(x);
	}
}
void build(int &x,int fa=0,int l=1,int r=n)
{
	if(l>r)return;
	int mid=(l+r)>>1;
	if(!x)newnode(x,fa,mid);
	if(l==r)return;
	build(t[x].ch[0],x,l,mid-1);
	build(t[x].ch[1],x,mid+1,r);
	pushup(x);
}
void erase(int val,int x=root)
{
	if(t[x].val==val)
	{
		splay(x);
		if(t[x].ch[1])
		{
			int p=t[x].ch[1];
			while(t[p].ch[0])p=t[p].ch[0];
			splay(p,x);	
			root=p,connect(t[x].ch[0],p,0),t[p].fa=0;
			pushup(p);
		}
		else root=t[x].ch[0],t[t[x].ch[0]].fa=0;
	}
	else if(t[x].val>val)erase(val,t[x].ch[0]);
	else erase(val,t[x].ch[1]);
}
int getrank(int k,int x=root)
{
	if(k<=0)
	{
		splay(x);
		return t[x].val;
	}
	if(k<=t[t[x].ch[0]].siz)return getrank(k,t[x].ch[0]);
	int tmp=k-t[t[x].ch[0]].siz-t[x].cnt;
	if(tmp<=0)
	{
		splay(x);
		return t[x].val;
	}
	return getrank(tmp,t[x].ch[1]);
}
inline int read()
{
	char ch=getchar();
	int sum=0,f=1;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-')f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		sum=(sum<<1)+(sum<<3)+(ch^48);
		ch=getchar();
	}
	return f*sum;
}
void write(int x)
{
	if(x<0)
	{
		putchar('-');
		x=-x;
	}
	if(x/10)write(x/10);
	putchar(x%10+'0');
}
int main()
{
	freopen("allin.in","r",stdin);
	freopen("allin.out","w",stdout);
	n=read();
	build(root);
	int num=1;
	for(int i=1;i<=n;i++)
	{
		int step;
		step=read();
		num=(num+step-1)%(n-i+1)+1;
		int ans=getrank(num);
		write(ans);
		putchar('\n');
		erase(ans);
	}
	return 0;
}

T4:

由于一些不可抗拒的原因,\(n\ge 22\) 无解。

那么只用考虑 \(n\le21\) 的情况即可。

由于 \(n\) 的范围缩小,导致状压又可以重新使用,所以考虑状压。

\(f_i\)\(i\) 中所有的集合能被表示的最小下标。

那么对于任何一位 \(j\) 如果在 \(i\) 中,那么:

\(f_i=\max(next(f_{i\oplus j},j))\hspace{0.2cm}\text{其中}\hspace{0.1cm}next(x,y)\hspace{0.1cm}\text{表示第一个在}\hspace{0.1cm}x\hspace{0.1cm}\text{后面出现的字符}\hspace{0.1cm} y\hspace{0.1cm}\text{的下标}\)

因为本集合中的所有情况都得满足,所以下标得取最大值。

那么我们只需预处理出来 \(next\) 即可。

从后往前枚举,本位的 \(next\) 可以由上位继承,也要取上一位,设字符串中 \(s_{i+1}\)\(c\) 那么:

\(next_{i,j}=next_{i+1,j}\)

\(next_{i,c}=i+1\)

预处理出来,然后状压 dp,dp 时对每个状态枚举 \(1\) 的位置即可。

详细看代码。

希望有大佬可以指明下为何 \(n\ge22\) 无解。

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
char s[505];
int n,vis[505][26],f[1<<21];
int main()
{
	freopen("factorial.in","r",stdin);
	freopen("factorial.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		memset(vis,0,sizeof(vis));
		memset(f,0,sizeof(f));
		scanf("%d",&n);
		scanf("%s",s+1);
		if(n>21)
		{
			printf("NO\n");
			continue;
		}
		int len=strlen(s+1);
		for(int i=0;i<n;i++)vis[len][i]=vis[len+1][i]=len+1;
		for(int i=len-1;i>=0;i--)
		{
			for(int j=0;j<n;j++)vis[i][j]=vis[i+1][j];
			vis[i][s[i+1]-'a']=i+1;
		}
		for(int i=0;i<1<<n;i++)
		{
			for(int j=0;j<n;j++)
			{
				if(i&(1<<j))
				{
					f[i]=max(f[i],vis[f[i^(1<<j)]][j]);
				}
			}
		}
		if(f[(1<<n)-1]!=len+1)printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}
posted @ 2023-02-24 13:54  Gmt丶Fu9ture  阅读(23)  评论(0)    收藏  举报