CSP-S 游记
前言
完完又蛋蛋,T2时间复杂度多一个log,T3B性质没写出来,T4只会状压的我真的还有救吗。
day 0
中午大部队就出发了,本来还说要跟队走的,但是最后发现没有女生,如果要去的话,疑似要和老师住,感觉非常恐怖,所以最后就没跟队,但是下午仍然回家了。
到家就开始摆烂了,中午玩了会手机,然后下午去我妈实验室逛了一圈,买了点零食,晚上依然摆烂,学习Air打了一会音游,成功被phi初级曲薄纱。
day 1
早上起的很晚,起来之后还玩了一会手机,然后就开车去郑州了。
中午在考场周边转了好几圈之后,毅然决定去吃姐弟俩土豆粉,吃完饭甚至还抽空去买了件衣服,也是没谁了。
然后就去考场等大部队过来,一起进考场。
赛时
进考场开始调dev,然后就是和dev斗智斗勇的10min,反正最后也配好了。
2:30开T1,发现直接贪心就是对的,于是我写写写,调调调,2:55的时候过了。
开T2,第一眼:这不最小生成树吗,直接暴力跑就是对的啊。
然后花了5min写了一个,发现过不去大样例,然后非常容易地就给自己卡掉了。接下来就是无穷无尽的胡思路,然后假掉。
然后开始想暴力,然后发现 \(m\) 太大,然后灵光一现,发现,只用保留 \(n-1\) 条原图的边即可。
然后就开始写,但是当时算时间复杂度的时候算假了,然后觉得排序放外面的话还有一个大常数,于是直接坚定地认为排序放里面和放外面是一个时间复杂度,然后就G了。写完大概4点多,大样例一遍过,但是本地跑得巨慢。
然后看T3和T4,暴力都是显然的,但是我T3疑似忘判是否相等了,哭。
写完还有1个半小时的时间,于是开始写特殊性质。
感觉T3的B性质是显然的,于是开始写写写,调调调,但是一直到最后半个小时都过不去大样例,最后放弃了。
期望得分 :\(100+80+0+20=200\)
民间数据 :\(100+80+30+20=230\)
CCF数据 :\(100+80+25+20=225\)
怎么民间数据还反向挂了30pts,我猜民间数据太水了导致我那个连长度是否相等都没判的代码都能获得30pts的高分。
感谢CCF,甚至没有卡我判长度相等。
NOIP是稳的。那没事了。
赛后
周日下午到机房开始订题,T2还RE了两发,但是问题不大,是我快读写挂了,我保证我考场上没有犯这种唐氏错误。
然后就没有什么波澜地拿到了80pts,改一下循环顺序就过了。
晚上八点多开始看T3,然后发现题解第一篇简直是个天才吧,但是想想发现自己会思路了不会AC自动机,这……
于是开始学习AC自动机,感觉还是比较容易的。
学完了就要调,然后自己造了个假的样例调,对着正确的玩意调了半个小时没有发现样例有锅。
周一早上来机房忽然发现这个问题,陷入沉默,然后又调了15min就过了。
感觉今年难度不小,但是自己写的也很唐,如果不瓜分的话,一等肯定是没有问题,但是luogu7级钩就有点困难了。
下午Air来机房了,然后经过某种神秘交流,发现Air的T3也没判相等,两个人一起G了。
不过听说郑州的那个女生CSP考得非常炸,虽然这样想不好,但是希望如此吧。
感觉可以提前开始写退役总结了。
就是反正高中因为OI停课大概率就只剩下20天左右的时间了。
后期不管拿没拿到一等,能不能进队,都不重要了,也不是不重要吧,就是也不会停课什么的了。
反正我的OI生涯就这样了。
反思
1.字符串真的该加强了,场上看到是字符串就开始想如何哈希写暴力,其实大概猜到了应该是要用个什么tri树或者AC自动机的,某一个关键性质也是推出来了的,至于为什么到最后也只有可能挂完了25pts呢,答案是我直接就是一个忘记了AC自动机是咋写的。
2.心态还是很重要的,考场上首先不要各种胡思乱想,就是安安心心写题是最好的,剩下的一切焦虑,不安,兴奋,都留到场下。然后对于如果一直假假假,不用慌,反正你思考也真的用不了那么久,如果有要用那么久的题,考场上其实也写不出来,只是思路假了,是不影响下一步胡出来正确的思路然后A掉这题的。
3.下次上考场前再也不喝饮料了,喝完饮料下午真的巨想上厕所。(虽然也不是正经反思吧,但是感觉和赛场上的策略什么的一样重要就是了)
4.不能一直懒惰,有些东西你看着它,明知道自己不会,但是犯懒,就是不想写,然后到考场上发现是这个东西,但是不会写,或者有某一个思路很像,但是细节就是想不出来,那就直接GG了。
5.要淡定,不要受别的东西的干扰。
6.考场上不论当时拿到了多少分,都不要怎么怎么慌张什么的,就是起码要保证自己思考的时候头脑是清醒的,否则的话,就很容易和CSP一样,直接忽略掉了其实时间复杂度是错误的。
题解
好了,破防完了,该订题了。
T1
你有 \(n\) 个人,\(3\) 个公司,每个人去不同公司的贡献不同,去同一个公司的人数不能超过 \(n/2\) 个,求能够拿到的最大收益。
显然,每个人都选最优的,最多只有一个公司会超,同时,如果你把这个公司超了的部分都扔给剩下两个公司的其中之一,剩下两个公司也不会超。
所以可以直接贪心。你先对于每个点,都假装它能选到最优的,然后看有没有公司超过,如果有的话,就贪心地选择那些选择次大值对答案影响最小的点。
然后就做完了。
T2
给你n个点,m条边,边有边权,然后还有k个可以用的中转点,每个中转点使用代价为 \(c_i\),然后和第 \(j\) 号点连边的代价为 \(a_{i,j}\)。求使前 \(n\) 个点都联通的最小代价。
首先80pts是容易的,你只需要 \(2^k\) 枚举当前使用了哪个中转点,然后往里面加边,之后sort一下,然后正常跑Kruskal即可。
然后你想优化,显然的一点是你可以把sort扔到外面,然后每次在里面直接暴力判断当前边能不能用即可。
然后就做完了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,K,ans,sum;
const int N=2e6+100,M=2e5+100,NM=1e4+10;
int a[11][NM],fa[N],c[N],flag[N];
struct node {
int u,v,w,fl;
friend bool operator <(node a,node b) {
return a.w<b.w;
}
}st[N],t[N];
int read() {
int t=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9') {
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
t=t*10+ch-'0';
ch=getchar();
}
return t*f;
}
int Get(int x) {
if(fa[x]==x) return x;
return fa[x]=Get(fa[x]);
}
void work() {
for(int i=1;i<=n+K;++i) {
fa[i]=i;
}
// sort(st+1,st+1+m);
for(int i=1;i<=m;++i) {
// cerr<<flag[0]<<" "<<m<<endl;
// if(t[i].fl==0) cerr<<"GG"<<endl;
if(!flag[t[i].fl]) continue;
// if(t[i].fl==0) cerr<<"GG"<<endl;
int u=t[i].u,v=t[i].v,w=t[i].w;
if(Get(u)!=Get(v)) {
fa[Get(u)]=Get(v);
sum+=w;
// cerr<<u<<" "<<v<<" "<<w<<" "<<m<<endl;
}
}
// cerr<<sum<<" "<<endl;
ans=min(ans,sum);
}
signed main() {
n=read();m=read();K=read();
// int u,v,w;
for(int i=1;i<=m;++i) {
st[i].u=read();st[i].v=read();st[i].w=read();st[i].fl=0;
}
sort(st+1,st+1+m);
for(int i=1;i<=n;++i) {
fa[i]=i;
}
int cnt=0;
for(int i=1;i<=m;++i) {
int u=st[i].u,v=st[i].v,w=st[i].w;
if(Get(u)!=Get(v)) {
fa[Get(u)]=Get(v);
ans+=w;
t[++cnt]=st[i];
// cerr<<u<<" "<<v<<" "<<w<<endl;
}
}
// cerr<<cnt<<endl;
for(int i=1;i<=K;i++) {
c[i]=read();
for(int j=1;j<=n;j++) {
a[i][j]=read();
t[++cnt].u=i+n;
t[cnt].v=j;
t[cnt].w=a[i][j];
t[cnt].fl=i;
}
}
// m=n-1;
sort(t+1,t+1+cnt);
// cerr<<cnt<<endl;
m=cnt;
for(int i=1;i<(1<<K);++i) {
sum=0;
// m=n-1;
for(int j=1;j<n;++j) {
st[j]=t[j];
}
flag[0]=1;
for(int j=1;j<=K;++j) {
if(i&(1<<(j-1))) {
sum+=c[j];
flag[j]=1;
}
else flag[j]=0;
}
// cerr<<i<<" "<<sum<<" ";
work();
}
cout<<ans<<endl;
return 0;
}
T3
给定 \(n\) 对字符串s1,s2。你可以用s2替换s1。有q次询问,每次给定 t1,t2,求使用一次替换操作使得 t1=t2的方案数。
首先,因为只能使用一次,所以你要替换的片段一定是包含了t1和t2不相等的所有点。
同时我们还要求,这段的前缀和后缀分别和t1里面这些位置的字符串相等。
那么我们就可以把s1看成是 A+B+D,把s2看成是 A+C+D,把t1看成a+b+d,把t2看成是a+c+d。
那么要求的答案就转变为求满足A+{+B+C+{+D是 a+{+b+c+{+d的子串的数量。
至于这个东西,你显然上个AC自动机即可。
事实上的话,你其实应该对于AC自动机找前缀取个和,这样时间复杂度才是正确的,下面那个代码能过纯属民间数据水了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,q,len=0,tot=0,ans=0,flag;
const int N=5e6+100;
char t1[N],t2[N],s1[N],s2[N],s[N],t[N];
int tri[N][28],cnt[N],fail[N],ru[N];
vector<pair<int,int> >o;
int read() {
int t=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9') {
if(ch=='-') f=-f;
ch=getchar();
}
while(ch>='0'&&ch<='9') {
t=t*10+ch-'0';
ch=getchar();
}
return t*f;
}
void add() {
int now=0;
for(int i=1;i<=len;i++) {
if(tri[now][s[i]-'a'+1]==0) {
tri[now][s[i]-'a'+1]=++tot;
}
now=tri[now][s[i]-'a'+1];
}
cnt[now]++;
}
void bfs() {
queue<int>q;
for(int i=1;i<=27;i++) {
if(tri[0][i]) {
fail[tri[0][i]]=0;
ru[0]++;
q.push(tri[0][i]);
}
}
while(q.size()) {
int x=q.front();q.pop();
// cerr<<x<<endl;
for(int j=1;j<=27;j++) {
if(tri[x][j]) {
fail[tri[x][j]]=tri[fail[x]][j];
ru[fail[tri[x][j]]]++;
q.push(tri[x][j]);
}
else {
tri[x][j]=tri[fail[x]][j];
}
}
}
}
void find() {
ans=0;
int now=0;
for(int i=1;i<=len;i++) {
int u=tri[now][t[i]-'a'+1];
while(u>1&&cnt[u]!=-1) {
ans+=cnt[u];
o.emplace_back(make_pair(cnt[u],u));
cnt[u]=-1;
u=fail[u];
}
now=tri[now][t[i]-'a'+1];
}
}
signed main() {
n=read();q=read();
for(int i=1;i<=n;i++) {
scanf("%s",s1+1);
scanf("%s",s2+1);
int m=strlen(s1+1);
int l=1,r=m;
while(s1[l]==s2[l]&&l<=n) {
l++;
}
while(s1[r]==s2[r]&&r>=1) {
r--;
}
if(l==m+1) {
continue;
}
for(int i=1;i<l;i++) {
s[i]=s1[i];
}
for(int i=r+1;i<=m;i++) {
s[i+2+(r-l+1)]=s1[i];
}
s[l]=s[r+(r-l+1)+2]=char('z'+1);
for(int i=l;i<=r;i++) {
s[i+1]=s1[i];
s[i+1+(r-l+1)]=s2[i];
}
len=m+r-l+3;
add();
}
bfs();
for(int i=1;i<=q;i++) {
scanf("%s",t1+1);
scanf("%s",t2+1);
int len1=strlen(t1+1);
int len2=strlen(t2+1);
if(len1!=len2) {
cout<<'0'<<'\n';
continue;
}
int l=1,r=len1;
while(t1[l]==t2[l]&&l<=len1) {
l++;
}
while(t1[r]==t2[r]&&r>=1) {
r--;
}
// if(l==len1+1)
for(int i=1;i<l;i++) {
t[i]=t1[i];
}
for(int i=r+1;i<=len1;i++) {
t[i+(r-l+1)+2]=t1[i];
}
t[l]=t[r+(r-l+1)+2]=char('z'+1);
for(int i=l;i<=r;i++) {
t[i+1]=t1[i];
t[i+1+(r-l+1)]=t2[i];
}
len=len1+(r-l+3);
// cerr<<len<<endl;
// flag=len-(len1-r);
find();
cout<<ans<<"\n";
for(auto y:o) {
cnt[y.second]=y.first;
}
o.clear();
}
return 0;
}
其实T3这个代码应该是跑不过官方数据的,但是反正民间数据很水,就不是很重要了。

浙公网安备 33010602011771号