Don't Never Around

【洛谷专栏】(这里是保存站版本)。

标题来自 KALPA 里的曲子 Don't Never Around。

昨晚脑壳发热想写一个属于自己的骗分技巧,仅供后人参考。

当然如果你实力足够强大理论上是不屑于骗分的,所以比较适合我这种不喜欢动脑子的人看。

等死,死国可乎?

反正都拿不到分,还不如放手一搏,相信暴力出奇迹!

本文写的都是校内的题,校外的题没什么骗分方法。

更新随缘看心情。

暴 力 美 学

强 力 剪 枝

这里的剪枝并非只有搜索意义上的剪枝,而是减少冗余的计算次数,使其除以一个巨大的常数,就像 std::bitsetw 一样。

FZUOJ#7482. 模糊匹配

神说,要宽容,然后就放过了暴力。

这题本身正解是什么分块,但是显然我不会。

k 的限制很宽容,可以减少一部分的枚举判断。

观察到一个性质:当 |pi|k 时,s 的每个地方都可以被成功模糊匹配,于是这种情况下的答案即 |s||pi|+1,于是加上这个关键剪枝就能过了。

//the code is from chenjh
#include<cstdio>
#include<cstring>
#define MAXN 200005
using namespace std;
int k,n,q,m;
char s[MAXN],t[MAXN];
int main(){
//	freopen("match.in","r",stdin);
//	freopen("match.out","w",stdout);
	scanf("%d %s %d",&k,s+1,&q);
	n=strlen(s+1);
	while(q--){
		scanf(" %s",t+1);
		m=strlen(t+1);
		if(m<=k){printf("%d\n",n-m+1);continue;}//这一个剪枝非常关键,决定了你能不能通过。
		int ans=0;
		for(int i=1,mi=n-m+1,l,r;i<=mi;i++){
			r=i+m-1;
			for(l=i;l+k<=r&&s[l]==t[l-i+1];l++);
			for(;(l<r&&r-l>=k)&&s[r]==t[r-i+1];--r);
			if(r-l<k)++ans;
		}
		printf("%d\n",ans);
	}
	return 0;
}

时间复杂度 O(|s|n),理论得分 30 分,实际得分 100 分。

部 分 重 构

在某些题目中,一次操作会影响部分,但是重新处理所有部分是不必要的,我们只需要处理被影响的那部分。

FZUOJ#8899. 小恐龙

坑,待填。

FZUOJ#9516. 流星雨

按照题意直接模拟流星和防御装置,时间复杂度 O(m2)

对于 l=r,L=R 的情况,可以直接点对点处理。

对于剩下的情况,我们可以合理猜测区间有交的概率很大,因此不会有很多对流星防御无效的装置,所以对于无效的就丢到一个栈中,询问结束时丢回去即可。

理论得分 25 分,实际得分 100 分。

//the code is from chenjh
#include<bits/stdc++.h>
#define MAXN 500005
using namespace std;
typedef long long LL;
typedef pair<LL,int> PLI;
const LL inf=0x3f3f3f3f3f3f3f3fll;
int n,m;
struct QUE{
	int h,l,r;
	LL d;
	QUE(const int _h=0,const int _l=0,const int _r=0,const LL&_d=0):h(_h),l(_l),r(_r),d(_d){}
}q[MAXN];
void solve1(){
	vector<QUE> v,w;v={QUE(0,1,n,inf)};
	for(int i=1;i<=m;i++){
		if(q[i].h==1) v.emplace_back(i,q[i].l,q[i].r,q[i].d);
		else if(q[i].h==2){
			w.clear();
			while(!v.empty()&&q[i].d){
				if(v.back().r>=q[i].l&&v.back().l<=q[i].r){
					printf("%d ",v.back().h);
					if(q[i].d<v.back().d) v.back().d-=q[i].d,q[i].d=0;
					else q[i].d-=v.back().d,v.pop_back();
				}
				else w.push_back(v.back()),v.pop_back();
			}
			reverse(w.begin(),w.end());
			v.insert(v.end(),w.begin(),w.end());
			putchar('\n');
			// for(const auto&[h,l,r,d]:v) fprintf(stderr,"D: %d %d %d %lld\n",h,l,r,d);
		}
	}
}
void solve4(){
	vector<QUE> v[MAXN];
	for(int i=1;i<=n;i++) v[i]={QUE(0,1,n,inf)};
	for(int i=1,L;i<=m;i++){
		L=q[i].l;
		if(q[i].h==1) v[L].emplace_back(i,q[i].l,q[i].r,q[i].d);
		else if(q[i].h==2){
			while(!v[L].empty()&&q[i].d){
				printf("%d ",v[L].back().h);
				if(q[i].d<v[L].back().d) v[L].back().d-=q[i].d,q[i].d=0;
				else q[i].d-=v[L].back().d,v[L].pop_back();
			}
			putchar('\n');
			// for(const auto&[h,l,r,d]:v) fprintf(stderr,"D: %d %d %d %lld\n",h,l,r,d);
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	bool sb4=1;
	for(int i=1;i<=m;i++)
		scanf("%d%d%d%lld",&q[i].h,&q[i].l,&q[i].r,&q[i].d),sb4&=q[i].l==q[i].r;
	if(sb4) solve4();
	else solve1();
	return 0;
}

理 性 分 析

通常时间复杂度分为最好时间复杂度,平均时间复杂度,最坏时间复杂度,我们考虑一道题是否能够通过都是看最坏时间复杂度。让一种算法达到最坏时间复杂度的时候通常是需要特殊构造的,但是这种特殊构造的数据一定会带有某些性质,对这种性质再单独处理就好了。

FZUOJ#9325. 水槽(sink)

可以用单调栈求出第一个比它小的值,因为在这些点的位置上水量才会有变化,建树后在随机数据下树高是 O(nlogn),。

考虑最坏的情况,当降序排序时,这种算法的时间复杂度会变为 O(n2),但是这种情况特殊处理一下即可。

理论分数 60 分,实际分数 100 分。

//the code is from chenjh
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<functional>
#define MAXN 1000005
using namespace std;
typedef long long LL;
int n,a[MAXN],p[MAXN],f[MAXN];
int stk[MAXN],t=0;
LL s[MAXN];
int main(){
//	freopen("sink.in","r",stdin);
//	freopen("sink.out","w",stdout);
	scanf("%d",&n);
	int x=1;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]<a[x]&&(x=i);
	if(n<=1000){
		LL s=0;
		memcpy(f+1,a+1,sizeof(int)*n);
		for(int i=1;i<=n;i++){
			rotate(f+1,f+n,f+n+1);
			s=0;
			for(int j=1;j<=n;j++) s+=(f[j]=min(f[j],a[j]));
			printf("%lld ",s);
		}
		return 0;
	}
	stk[++t]=p[x]=x;
	for(int i=x-1;i>0;--i){
		while(t&&a[i]<a[stk[t]]) --t;
		p[i]=stk[t],stk[++t]=i;
	}
	for(int i=n;i>x;--i){
		while(t&&a[i]<a[stk[t]]) --t;
		p[i]=stk[t],stk[++t]=i;
	}
	LL ans=0;
	if(is_sorted(a+1,a+n+1,greater<int>())){
		for(int i=1;i<=n;i++) s[1]+=a[i],s[i]-=a[i];
		for(int i=1;i<=n;i++) printf("%lld ",(s[i]+=s[i-1])+(LL)i*a[n]);
		return 0;
	}
	for(int i=1;i<=n;ans+=a[i++])if(i!=x)for(int u=i,e=0;u!=x;u=p[u]) s[e+=p[u]>u?p[u]-u:n-u+p[u]]+=a[p[u]]-a[u];
	for(int i=1;i<=n;i++) printf("%lld ",ans+=s[i]);
	return 0;
}

人 类 智 慧

局 部 最 优

有的时候会需要求解一些全局性的最优问题,却又不能直接全局贪心的时候,我们可以局部的贪心,去枚举部分局部最小值来取最优答案,在随机数据下有大概率是正确的。

FZUOJ#9495. 公交

正解是维护凸包做动态规划,我也不会。

这是一个来自 P1452 【模板】旋转卡壳 的技巧,这一题需要求的是凸包直径,于是随机旋转一个角度再按 x,y 排序,然后再选前面的几个和后面的几个答案取 x2+y2 最大值。

这题同样要求 mini=1n((j=1idj)+miv),所以有一个必然比较小。

又因为 di1,所以 i 越大 j=1idj 越大,所以我们就取前 Bi 和前 B 小的 mi 就可以了。B 考场上取得 800,下来实测 350 可过。

时间复杂度 O(qBlogn),理论 40 分,实际 100 分。

//the code is from chenjh
#include<bits/stdc++.h>
#define MAXN 100001
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
using ci=const int;
ci blk=350;
const LL inf=0x3f3f3f3f3f3f3f3fll;
int n,q;
int d[MAXN],m[MAXN];
LL ds[MAXN];
struct QUE{
	int op,p,f,v;
}e[MAXN];
void up(int x,const LL&v){for(;x<=n;x+=x&-x) ds[x]+=v;}
LL as(int x){LL ret=0;for(;x;x^=x&-x)ret+=ds[x];return ret;}
set<PII> S; 
int main(){
	ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
	cin>>n>>q;
	for(int i=1;i<=n;i++) cin>>d[i];
	for(int i=1;i<=n;i++) cin>>m[i];
	if(1ll*n*q<=4000*4000){
		for(int op,p,f,v;q--;){
			cin>>op>>p>>f>>v;
			if(op==1) d[p]=f;
			else if(op==2) m[p]=f;
			LL ans=inf,s=0;
			for(int i=1;i<=n;i++) ans=min(ans,(s+=d[i])+(LL)m[i]*v);
			cout<<ans<<'\n';
		}
		return 0;
	}
	for(int i=1;i<=q;i++) cin>>e[i].op>>e[i].p>>e[i].f>>e[i].v;
	for(int i=1;i<=n;i++){
		S.emplace(m[i],i),up(i,d[i]);
		if(S.size()>blk) S.erase(--S.end());
	}
	for(int i=1,op,p,f,v;i<=q;i++){
		op=e[i].op,p=e[i].p,f=e[i].f,v=e[i].v;
		if(op==1) up(p,f-d[p]),d[p]=f;
		else if(op==2){
			auto it=S.find(PII(m[p],p));
			if(it!=S.end()) S.erase(it);
			S.emplace(m[p]=f,p);
			if(S.size()>blk) S.erase(--S.end());
		}
		LL ans=inf;
		for(const PII&x:S) ans=min(ans,as(x.second)+(LL)x.first*v);
		for(int i=1,mi=min(n,blk);i<=mi;i++) ans=min(ans,as(i)+(LL)m[i]*v);
		cout<<ans<<'\n';
	}
	return 0;
}

FZUOJ#9942. 计算 calc

很容易证明在 x,y 确定后 ci 是可以三分或者分类讨论 O(1) 计算的。

m=maxi=1nmax(ai,bi),我们设枚举的 x,y 上限为 k=m。于是得到了一个 O(k2nlogm) 的算法。

然后可以通过前两个样例,同时我们发现答案最小的 x,y 并不是很大,于是我们直接取 k=700 即可,发现可以通过全部大样例,于是猜测数据强度和样例大致相同。

理论得分 20 分,实际得分 100 分。

//the code is from chenjh
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
inline LL f(const int a,const int b,const int x,const int y,const int c){return abs(a-(LL)c*x)+abs(b-(LL)c*y);}
int n,a[33],b[33];
int main(){
	scanf("%d",&n);
	int mr=0;
	LL ans=0;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),mr=max(mr,a[i]),ans+=a[i];
	for(int i=1;i<=n;i++) scanf("%d",&b[i]),mr=max(mr,b[i]),ans+=b[i];
	for(int x=0,mx=min(mr,700);x<=mx;x++)for(int y=0;y<=mx;y++){
		LL sum=0;
		for(int i=1;i<=n;i++){
			int l=0,r=mr;
			for(int mid;l<r;) mid=(l+r)>>1,f(a[i],b[i],x,y,mid)>f(a[i],b[i],x,y,mid+1)?l=mid+1:r=mid;
			sum+=f(a[i],b[i],x,y,l);
		}
		ans=min(ans,sum);
	}
	printf("%lld\n",ans);
	return 0;
}

特 殊 构 造

必 要 答 案

当一个构造题无法下手的时候,可以考虑构造他的必要答案。

什么是必要答案呢?即限制比题目条件更加严格,通俗来说就是构造出来的答案一定满足题目条件,但是在更严格的限制条件下构造无解时题面不一定无解。

有的构造题并不是那么好造数据,所以构造出来有解多半就是有解,无解就是无解,这样可以混到一个比较好看的分数。

FZUOJ#9341. 简单题(normal)

对于 m2×104 的情况,可以直接进行二分图染色构造方案,时间复杂度 O(m2)

对于剩下的情况,猜测有解的数据情况并不好造。

把所有线段按左端点排序,尝试贪心修建在上方,上方不行就尝试修建在下方,两边都不行就返回无解。

理论得分 40 分,实际得分 100 分。

//the code is from chenjh
#include<bits/stdc++.h>
#define MAXN 100001
using namespace std;
//typedef pair<int,int> PII;
int n,m;
struct SEG{
	int l,r,i;
	bool operator < (const SEG&B)const{return l==B.l?r>B.r:l<B.l;}
}a[MAXN];
short c[MAXN];
stack<int,vector<int> > stk[2];
void dfs(const int u,const int o){
	c[u]=o;
	for(int v=1;v<=m;v++)
		if((a[u].l<a[v].l&&a[v].l<a[u].r&&a[u].r<a[v].r)||(a[v].l<a[u].l&&a[u].l<a[v].r&&a[v].r<a[u].r)){
			if(!c[v]) dfs(v,o^3);
			else if(c[v]!=(o^3)) puts("IMPOSSIBLE"),exit(0);
		}
}
int main(){
//	freopen("normal.in","r",stdin);
//	freopen("normal.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++) scanf("%d%d",&a[i].l,&a[i].r),a[i].i=i;
	if(m<=20000){
		for(int i=1;i<=m;i++)if(!c[i]) dfs(i,1);
		for(int i=1;i<=m;i++) puts(c[i]==1?"N":"S");
		return 0;
	}
	sort(a+1,a+m+1);
	stk[0].push(n+1),stk[1].push(n+1);
	for(int i=1;i<=m;i++){
//		fprintf(stderr,"%d %d\n",a[i].l,a[i].r);
		while(stk[0].top()<=a[i].l) stk[0].pop();
		while(stk[1].top()<=a[i].l) stk[1].pop();
		if(a[i].r<=stk[0].top()) stk[0].push(a[i].r),c[a[i].i]=0;
		else if(a[i].r<=stk[1].top()) stk[1].push(a[i].r),c[a[i].i]=1;
		else{
			sort(a+1,a+m+1,[](const SEG&A,const SEG&B){return A.i<B.i;});
			for(int i=1;i<=m;i++)if(!c[i]) dfs(i,1);
			for(int i=1;i<=m;i++) puts(c[i]==1?"N":"S");
			return 0;
		}
	}
	for(int i=1;i<=m;i++) puts(c[i]?"N":"S");
	return 0;
}

posted @   Chen_Jinhui  阅读(69)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)

一言

你将不再是道具,而是成为人如其名的人。
——紫罗兰的永恒花园
点击右上角即可分享
微信分享提示