20240803

赛时得分

题目 A B C D 总分 排名 比例
满分 100 100 100 100 400 172 100%
得分 50 20 0 10 80 129 75.0%

A. 英俊奶牛来拍照(70/100)

已经算是运气比较好了,赛时其实读错题了,但仍然拿到了 \(\text{50pts}\)。当时以为最多只能改一个白色奶牛的颜色,补题的时候才发现其实是可以改不限次。

\(\text{70%}\) 得分做法,一个朴素的暴力,直接 \(\mathcal{O}(n^2)\) 枚举从第 \(i\) 个位置开始,符合条件的最远位置,计算 WS 的个数 s0s1,当满足 s0-s1>=0 and (s0-s1)%2==0 时,此时的答案就是合法的。

本来这个做法是 \(\text{65%}\) 得分的,但暴力的时候我们加上一句 if((a[n].x-a[i].x)<maxn) break; 就可以提前结束循环,因为这题的特性就是从头开始大概率就长,果然可以多过一个数据点。

这题也是我第一次使用对拍的题。纪念一下。

#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
using namespace std;
const int N=1e5+1;
ll n,s0,s1;
ll st,ed,len,maxn=-1;
char c;
struct lhm
{
	ll x,col;
}a[N];
bool cmp(lhm a,lhm b)
{
	return a.x<b.x;
}
int main()
{
	freopen("photo.in","r",stdin);
	freopen("photo.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].x>>c;
		if(c=='W') a[i].col=0;
		else a[i].col=1;
	}
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=n;i++)
	{
		s0=0,s1=0;
		st=a[i].x;
		if((a[n].x-a[i].x)<maxn) break;
		for(int j=i;j<=n;j++)
		{
			if(a[j].col==1) s1++;
			else s0++;
			if(s0-s1>=0 and (s0-s1)%2==0)
			{
				ed=a[j].x;
				len=ed-st;
				maxn=max(maxn,len);
			}
		}
	}
	cout<<maxn;
	return 0;
}

B. 屏蔽网站(100/100)

这题赛时没做上来其实太可惜了,直观上来看还是对哈希的用处不太习惯。其实这题是 Trie 树的板子题,但众所周知,lhm 讨厌一切与树和图有关的东西,所以学好哈希很重要。

其实这题真的挺好想的。做出来这场就刷新个人最好成绩了。

赛时的 \(\text{20%}\) 得分做法,其实就是一份水灵灵的暴力,开两个 set<string> 分别存一下要保存的和要屏蔽的字符串,然后拿出要屏蔽的串逐位判断,对于每个保存的串里面的每一位,找出有相同字符最多的那个串再加上下一位,扔进第三个 set<string> ans 里维护,由于 set 自动按字典序排序,所以就解决了。这个复杂度一眼是 \(\mathcal{O}(n^2)\) 的,详细展开是 \(\mathcal{O}(\dfrac{1}{4}nl)\) 的,\(l\) 指的是字符总长度,但数量级是不变的,总之过不了就对了。

#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
using namespace std;
ll n,lst;
char c;
string ins,res;
set<string> s,bc,pb,ans;
string pd(string a,string b)
{
	string mk;
	for(int i=0;;i++)
	{
		if(a[i]==b[i]) mk+=a[i];
		else
		{
			mk+=a[i];
			return mk;
		}
	}
}
int main()
{
	freopen("website.in","r",stdin);
	freopen("website.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>c>>ins;
		if(c=='+') bc.insert(ins);
		else pb.insert(ins);
	}
	for(auto i:pb)
	{
		lst=0;
		for(auto j:bc)
		{
			res=pd(i,j);
			if(lst>res.length()) break;
			lst=res.length();
			s.insert(pd(i,j));
		}
		ans.insert(*--s.end());
	}
	cout<<ans.size()<<endl;
	for(auto i:ans) cout<<i<<endl;
	return 0;
}

\(\text{100%}\) 得分做法,对于每一个要保存的串,我们把它们的所有前缀的哈希值存在一个 set<ll> 里,这样就方便我们进行 \(\mathcal{O}(1)\) 查询了。接着把所有要保存的串扔在一个 set<string> 里,然后在读入结束后另开循环枚举每一个要屏蔽的串的所有前缀哈希值,如果到某一位哈希值在 set<ll> 中没有了,我们就把原字符串到这一位的部分截出来扔到 set<ll> ans 里即可。

#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
#define ull unsigned long long
using namespace std;
const int N=2e5+1,base=233;
ll n,len,hsh;
ull h[N],p[N];
char c;
string s,mk;
set<ll> bc;
set<string> pb,ans;
void makehash(string s,ull h[],ull p[])
{
    p[0]=1;
    s=" "+s;
    ll l=s.length()-1;
    for(int i=1;i<=l;i++)
    {
        h[i]=h[i-1]*base+s[i];
        p[i]=p[i-1]*base;
    }
    return;
}
ull gethash(string s,ull h[],ull p[],ll l,ll r)
{
    return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
    freopen("website.in","r",stdin);
    freopen("website.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>c>>s;
        if(c=='+')
        {
            len=s.length();
            makehash(s,h,p);
            for(int i=1;i<=len;i++) bc.insert(gethash(s,h,p,1,i));
        }
        else pb.insert(s);
    }
    for(auto i:pb)
    {
        len=i.length();
        makehash(i,h,p);
        for(int j=1;j<=len;j++)
        {
            hsh=gethash(i,h,p,1,j);
            if(bc.find(hsh)==bc.end())
            {
                mk=i.substr(0,j);
                ans.insert(mk);
                break;
            }
        }
    }
    cout<<ans.size()<<endl;
    for(auto i:ans) cout<<i<<endl;
    return 0;
}

其实通过这题以及 20240731 那场的 B 题神奇的度度熊这两道题总和来看,不难发现字符串匹配题的常规做法就是把模板串的哈希值进行预处理,将模板串单独预处理完后再与子串或者匹配串进行哈希匹配操作,离线维护大概率会用到 STL。这样一来大部分字符串匹配题我们都可以尝试用 hash 和 KMP 解决了。至于 Trie 树?等我退役了再学吧。


C. 又见最小生成树(0/100)

垃圾图论题。什么玩意又见最小生成树,我跟你熟吗?我什么时候见过你?

全输出 0 竟然一分都不给我,今年省选 Day2 B 题我全输出 1 好歹送我 \(5\) 分,你真就一分不给呗?我破大防。


D. 排列(10/100)

恶心图论题。还有那个 C 更恶心。讨厌图论。

其实这题我都不好意思搬上来写题解。赛时写的这个 \(\text{10%}\) 得分做法其实是一个假的暴力,只不过写的精度还比较可观,误差不是很大,然后正好也就水过去了第一组数据。

挂一下这个不雅观的代码。

upd(lhm 在写题解时):写的跟屎一样,我自己都看不明白赛时在写一些什么东西。

#include<bits/stdc++.h>
#define Std_Maker lhm
#define ll long long
using namespace std;
const int N=1e5+1;
ll n,ans,minn;
set<ll> s;
bool ok=0;
struct lhm
{
	ll num,l,r,v;
	multiset<ll> inc;
}a[N],b[N];
bool cmp(lhm a,lhm b)
{
	if(a.v==b.v) return a.l>b.l;
	return a.v>b.v;
}
int main()
{
	freopen("permutation.in","r",stdin);
	freopen("permutation.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i].l>>a[i].r>>a[i].v;
		b[i].l=a[i].l,b[i].r=a[i].r,b[i].v=a[i].v;
		a[i].num=i;
	}
	if(n<=21)
	{
		sort(a+1,a+n+1,cmp);
		for(int i=1;i<=n;i++)
		{
			s.clear();
			for(int j=a[i].l;j<=a[i].r;j++) s.insert(b[j].v);
			for(auto k:s)
			{
				for(int j=1;j<=n;j++)
				{
					ok=0;
					if(a[j].v==k and a[i].num!=a[j].num)
					{
						if(a[i].inc.find(k)==a[i].inc.end())
						{
							a[j].inc.insert(a[i].v);
							ok=1;
							break;
						}
						else continue;
					}
				}
				if(ok==1) break;
			}
		}
		for(int i=n;i>=1;i--)
		{
			for(auto j:a[i].inc) ans+=j;
		}
		cout<<ans;
	}
	else
	{
		sort(a+1,a+n+1,cmp);
		for(int i=1;i<=n;i++) ans+=a[i].v;
		cout<<ans;
	}
	return 0;
}
posted @ 2024-08-03 15:32  Lithium_Chestnut  阅读(7)  评论(0)    收藏  举报