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

浙公网安备 33010602011771号