Logo zrl123456 的博客

博客

标签
暂无

[ABC344D] String Bags 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-12 20:25:36

题目
正解:dp。
歪解:枚举,剪枝。(这告诉我们暴力出奇迹)


我们可以开一个 vector,第一个值是拼成的字符串,第二个值是支付的日元数,然后存储所有的可能,但时间空间根本不允许。
那么我们可以考虑剪枝,当以下情况满足一种的时候,字符串就不用存储:

  1. 字符串出现过且之前的最少日元数比现在少;
  2. 该字符串不等于对于任何一个 $x$ 的 $T_{1\dots x}$。

剩下就没了,但需要注意:在每个袋子里最多选一个
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 214748364721474836
using namespace std;
inl int read(){
    int f=1, x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9'){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return f*x;
}
inl void write(int x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>=10) write(x\/10);
    putchar(x%10^48);
    return;
}
#define N 105
#define A 15
string t,s[N][A];
int n,a[N],ans=INF;
vector<pair<string,int> >g;
map<string,int>mp;
inl bool cstrstr(string a,string b){
	for(int i=0;i<a.size();++i)
		if(a[i]!=b[i]) return false;
	return true;
}
signed main(){
	cin>>t;
	n=read();
	g.push_back({"",0});
	for(int i=1;i<=n;++i){
		a[i]=read();
		for(int j=1;j<=a[i];++j) cin>>s[i][j];
	}
	for(int i=1;i<=n;++i){
		int siz=g.size();
		string str="";
		for(int j=1;j<=a[i];++j)
			for(int k=0;k<siz;++k){
				str=g[k].first+s[i][j];
				if(str.size()<=t.size()&&(!mp[str]||mp[str]>g[k].second+1)&&cstrstr(str,t)){
					g.push_back({str,g[k].second+1});
					mp[str]=g[k].second+1;
				}
			}
	}
	for(int i=0;i<g.size();++i)
		if(g[i].first==t) ans=min(ans,g[i].second);
	if(ans==INF) puts("-1");
	else write(ans);
    return 0;
}

P3372 【模板】线段树 1? 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-16 11:12:15

题目
(用树状数组写线段树)


树状数组 1 是单点修改区间查询,树状数组 2 是区间修改单点查询,那么怎么实现区间修改区间查询呢?
既然要区间修改,那么我们依然要用差分(设原数组为 $a$,差分数组为 $p$):
$$\begin{aligned}\sum_{i=1}^na_i&=\sum_{i=1}^n\sum_{j=1}^ip_j\&=\sum_{i=1}^n(n-i+1)p_i\end{aligned}$$ 但由于 $n$ 是不固定的,我们无法快速得到 $\sum_{i=1}^n(n-i+1)p_i$。
正难则反。
$$\begin{aligned}\sum_{i=1}^n(n-i+1)p_i&=\sum_{i=1}^nnp_i-(i-1)p_i\&=(\sum_{i=1}^nnp_i)-(\sum_{i=1}^n(i-1)p_i)\&=n(\sum_{i=1}^np_i)-(\sum_{i=1}^n(i-1)p_i)\end{aligned}$$ 那么,我们需要维护两棵树状数组,一颗存储 $\sum_{i=1}^np_i$(我们称为 $b$),一颗存储 $\sum_{i=1}^n(i-1)p_i$(我们称为 $c$),那么在每次修改的时候:

b.add(l,k);
b.add(r+1,-k);
c.add(l,(l-1)*k);
c.add(r+1,-r*k);

每次查询的结果就是:
$$\begin{aligned}\sum_{i=l}^{r}a_i&=(\sum_{i=1}^ra_i)-(\sum_{i=1}^{l-1}a_i)\&=(r(\sum_{i=1}^rp_i)-(\sum_{i=1}^r(i-1)p_i))-((l-1)(\sum_{i=1}^{l-1}p_i)-(\sum_{i=1}^{l-1}(i-1)p_i))\end{aligned}$$

int sumr=r*b.getsum(r)-c.getsum(r),suml=(l-1)*b.getsum(l-1)-c.getsum(l-1);
write(sumr-suml);

总体复杂度 $O(n\log n)$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 214748364721474836
#define rep(i,x,y) for(int i=x;i<=y;++i) 
using namespace std;
inl int read(){
    int f=1, x=0;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(isdigit(ch)){
        x=(x<<1)+(x<<3)+(ch^48);
        ch=getchar();
    }
    return f*x;
}
inl void write(int x){
    if(x<0){
        putchar('-');
        x=-x;
    }
    if(x>=10) write(x\/10);
    putchar(x%10^48);
    return;
}
const int N=5e5+5;
int n,m,a[N],opt,l,r,k;
struct BIT{
	int p[N];
	inl int lowbit(int x){
		return x&(-x);
	}
	inl void add(int x,int k){
		while(x<=n){
			p[x]+=k;
			x+=lowbit(x);
		}
		return;
	}
	inl int getsum(int x){
		int sum=0;
		while(x){
			sum+=p[x];
			x-=lowbit(x);
		}
		return sum;
	}
}b,c;
signed main(){
	n=read();
	m=read();
	rep(i,1,n){
		a[i]=read();
		b.add(i,a[i]-a[i-1]);
		c.add(i,(i-1)*(a[i]-a[i-1]));
	}
	rep(i,1,m){
		opt=read();
		if(opt==1){
			l=read();
			r=read();
			k=read();
			b.add(l,k);
			b.add(r+1,-k);
			c.add(l,(l-1)*k);
			c.add(r+1,-r*k);
		}else{
			l=read();
			r=read();
			int sumr=r*b.getsum(r)-c.getsum(r),suml=(l-1)*b.getsum(l-1)-c.getsum(l-1);
			write(sumr-suml);
			puts("");
		}
	}
    return 0;
}

[ABC345E] Colorful Subsequence 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-21 22:09:24

一眼 dp (贪心)

部分分 (有吗)

我们通过思考发现贪心策略是不行的,那么我们考虑 dp。

设状态:

我们设 $f_{i,j}$ 为循环到第 $i$ 个球且选中了第 $i$ 个球,并有 $j$ 个球被删除能所得的最大价值(若取不到值便为负无穷)。
边界条件是 $f_{0,0}=0$,最后的答案是什么我们还需要思考一下:
故最后答案为:
$$ans=\max_{i=n-k}^n(f_i)$$

设状态转移方程:

那么我们可以寻找一个球 $p\ (j-1\le p\le i-1)$ 和一个值 $f_{p,j-1}$,也就是说我们假设倒数第二个没有被删除的球为 $p$,易得转移方程:
$$f_{i,j}=\max_{p=j-1,c_p\ne c_i}^{i-1}(f_{p,j-1})+v_i$$ 这样时间复杂度就是 $O(nk^2)$,考虑优化。

优化:

如果没有 $c_p\ne c_i$ 的限制的话,这道题就变得非常简单,只需要边维护 $f$ 数组,边计算最大值即可。
但这只是假设,有了这条限制我们该怎么办呢?
解决方法当然有,我们找到一个次大值 $p'$,使 $c_{p'}\ne c_p$ 即可。
为什么呢?

  • 若 $c_p=c_i$,则 $c_{p'}\ne c_i$,$c_{p'}$ 为最优解。
  • 若 $c_p\ne c_i$,则 $c_p$ 本身就为最优解。

那么问题又来了,如何转移(定义最大值为 $fmax$,次大值为 $smax$,结尾 $c$ 值为 $lst$,由于空间复杂度较大,故后面 $f$ 压掉第一维)?

  • 若 $lst\ne c_j$,则:
    $f_j=fmax+v_j$,更新 $f_j$。
    以前的 $f_j\ge fmax$:$smax=fmax,fmax=f_j,lst=c_j$。
    若 $f_j\ge smax$:$smax=f_j$。
  • 若 $lst=c_j$,则:
    $f_j=smax+v_j$,更新 $f_j$。
    若 $f_j\ge fmax$:$fmax=f_j$。

这样,时间复杂度为 $O(nk)$,空间复杂度为 $O(n)$,可以通过。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 214748364721474836
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define T set<string>::iterator
using namespace std;
const int N=2e5+5;
int n,k,c[N],v[N],f[N],ans=-INF;
signed main(){
	fst;
	memset(f,-0x3f,sizeof(f));
	f[0]=0;
	cin>>n>>k;
	rep(i,1,n) cin>>c[i]>>v[i];
	rep(i,1,n-k){
		int first_max=f[i-1],second_max=-INF,copyc=c[i-1];
		rep(j,i,i+k){
			int copyf=f[j];
			if(c[j]!=copyc){
				f[j]=first_max+v[j];
				if(copyf>=first_max){
					second_max=first_max;
					first_max=copyf;
					copyc=c[j];
				}else if(copyf>=second_max) second_max=copyf;
			}else{
				f[j]=second_max+v[j];
				if(copyf>=first_max) first_max=copyf;
			}
		}
	}
	rep(i,n-k,n) ans=max(ans,f[i]);
	if(ans>=0) cout<<ans;
	else cout<<-1;
    return 0;
}

[ABC346D] Gomamayo Sequence 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-28 21:13:16

题目
一眼 dp。

做法一:

从一端开始 dp,设 $f_{i,0/1,0/1}$ 为进行到第 $i$ 位,最后一位为 $0/1$,且是或不是($0/1$)好字符串。
易得转移方程:
$$f_{i,0,0}=f_{i-1,1,0}+[s_i=1]\times c_i$$ $$f_{i,1,0}=f_{i-1,0,0}+[s_i=0]\times c_i$$ $$f_{i,0,1}=\min(f_{i-1,1,1},f_{i-1,0,0})+[s_i=1]\times c_i$$ $$f_{i,1,1}=\min(f_{i-1,0,1},f_{i-1,1,0})+[s_i=0]\times c_i$$ 初始化:
$$f_{2,0,0}=[s_1=0]\times c_1+[s_2=1]\times c_2$$ $$f_{2,1,0}=[s_1=1]\times c_1+[s_2=0]\times c_2$$ $$f_{2,0,1}=[s_1=1]\times c_1+[s_2=1]\times c_2$$ $$f_{2,1,1}=[s_1=0]\times c_1+[s_2=0]\times c_2$$ 直接放代码:

#include<bits\/stdc++.h>
#define int long long
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define repe(i,x,y,z) for(int i=x;i<=y;i+=z)
#define eper(i,x,y,z) for(int i=x;i>=y;i-=z)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define inl inline
#define INF LLONG_MAX
using namespace std;
const int N=2e5+5;
int n,c[N],f[N][5][5];
string s;
signed main(){
	fst;
	memset(f,0x3f,sizeof(f));
	cin>>n>>s;
	s='2'+s;
	rep(i,1,n) cin>>c[i];
	f[2][0][0]=(s[1]=='0')*c[1]+(s[2]=='1')*c[2];
	f[2][1][0]=(s[1]=='1')*c[1]+(s[2]=='0')*c[2];
	f[2][0][1]=(s[1]=='1')*c[1]+(s[2]=='1')*c[2];
	f[2][1][1]=(s[1]=='0')*c[1]+(s[2]=='0')*c[2];
	rep(i,3,n){
		f[i][0][0]=f[i-1][1][0]+(s[i]=='1')*c[i];
		f[i][1][0]=f[i-1][0][0]+(s[i]=='0')*c[i];
		f[i][0][1]=min(f[i-1][1][1],f[i-1][0][0])+(s[i]=='1')*c[i];
		f[i][1][1]=min(f[i-1][0][1],f[i-1][1][0])+(s[i]=='0')*c[i];
	}
\/\/	rep(i,1,n) cout<<f[i][0][0]<<' '<<f[i][0][1]<<' '<<f[i][1][0]<<' '<<f[i][1][1]<<endl;
	cout<<min(f[n][0][1],f[n][1][1]);
	return 0;
}

做法二:

考虑好字符串的本质,实际就是中间两位相同的两个交错字符串,所以我们可以从两端进行 dp,设 $f_{i,0/1}$ 为以第 $i$ 为结尾,且结尾是 $0/1$ 的前缀 dp;设 $g_{i,0/1}$ 为以第 $i$ 位开头,且开头为 $0/1$ 的后缀 dp。
转移方程与做法一类似,直接放代码:

#include<bits\/stdc++.h>
#define int long long
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define repe(i,x,y,z) for(int i=x;i<=y;i+=z)
#define eper(i,x,y,z) for(int i=x;i>=y;i-=z)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define inl inline
#define INF LLONG_MAX
using namespace std;
const int N=2e5+5;
int n,c[N],f[N][5],g[N][5],ans=INF;
string s;
signed main(){
	fst;
	cin>>n>>s;
	s=' '+s;
	rep(i,1,n) cin>>c[i];
	rep(i,1,n){
		f[i][0]=(s[i]=='1')*c[i]+f[i-1][1];
		f[i][1]=(s[i]=='0')*c[i]+f[i-1][0];
	}
	per(i,n,1){
		g[i][0]=(s[i]=='1')*c[i]+g[i+1][1];
		g[i][1]=(s[i]=='0')*c[i]+g[i+1][0];
	}
	rep(i,1,n-1){
		ans=min(ans,f[i][0]+g[i+1][0]);
		ans=min(ans,f[i][1]+g[i+1][1]);
	}
	cout<<ans;
	return 0;
}

分析背包的三要素

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-29 16:06:27

分析背包的三要素

背包的三要素分别是:物品,体积和价值。

例题

例题 1

题目大意:

$n$ 个物品,选取其中若干个物品,使得对选取的这些物品 $\sum w_i\leq W$ 的前提下最大化 $\sum v_i$。

数据范围:

  • $N\in[1,100]$
  • $W\in[1,10^5]$
  • $w_i\in[1,W]$
  • $v_i\in[1,10^9]$

做法:

这道题就是一个 01 背包的板子。
我们就设 $f_{i,j}$ 为前 $i$ 个物品装入容量为 $j$ 的背包里,所能获取的最大价值。
易得状态转移方程:
$$f_{i,j}=\max(f_{i-1,j-w_i}+v_i,f_{i-1,j})$$ 由于数据范围过小,不需要使用滚动数组即可通过。
代码:

#include<bits\/stdc++.h>
#define int long long
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define repe(i,x,y,z) for(int i=x;i<=y;i+=z)
#define eper(i,x,y,z) for(int i=x;i>=y;i-=z)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define inl inline
#define INF LLONG_MAX
using namespace std;
const int N=105,W=1e5+5;
int n,w,ww[N],v[N],f[N][W];
signed main(){
	fst;
	cin>>n>>w;
	rep(i,1,n) cin>>ww[i]>>v[i];
	rep(i,1,n){
		rep(j,0,ww[i]-1) f[i][j]=f[i-1][j];
		rep(j,ww[i],w) f[i][j]=max(f[i-1][j-ww[i]]+v[i],f[i-1][j]);
	}
	cout<<f[n][w];
	return 0;
}

例题 2

题目大意:

$n$ 个物品,选取其中若干个物品,使得对选取的这些物品 $\sum w_i\leq W$ 的前提下最大化 $\sum v_i$。

数据范围:

  • $N\in[1,100]$
  • $W\in[1,10^9]$
  • $w_i\in[1,W]$
  • $v_i\in[1,10^3]$

TLE/MLE 做法:

参照上述做法,$O(NW)$ 暴力做法,但会 TLE/MLE。

正解:

注意到 $v_i$ 较小,我们可以尝试让 $v_i$ 当背包和物品的体积,$w_i$ 当价值。
我们设 $f_{i,j}$ 为前 $i$ 个物品的体积为 $j$,使最后的价值最小。
得状态转移方程:
$$f_{i,j}=\min(f_{i-1,j-v_i}+w_i,f_{i-1,j})$$ 最后直接倒序循环查找,找到第一个 $f_{n,j}\le w$ 的,直接输出即可。
代码:

#include<bits\/stdc++.h>
#define int long long
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define repe(i,x,y,z) for(int i=x;i<=y;i+=z)
#define eper(i,x,y,z) for(int i=x;i>=y;i-=z)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define inl inline
#define INF LLONG_MAX
using namespace std;
const int N=105,W=1e5+5;
int n,w,ww[N],v[N],f[N][W],sum;
signed main(){
	fst;
	cin>>n>>w;
	memset(f,0x3f,sizeof(f));
	rep(i,1,n){
		cin>>ww[i]>>v[i];
		sum+=v[i];
	}
	f[0][0]=0;
	rep(i,1,n){
		rep(j,0,v[i]-1) f[i][j]=f[i-1][j];
		rep(j,v[i],sum) f[i][j]=min(f[i-1][j-v[i]]+ww[i],f[i-1][j]);
	}
	per(i,sum,0)
		if(f[n][i]<=w){
			cout<<i;
			return 0; 
		}
	return 0;
}

背包的转化

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-03-30 11:15:32

背包的转化

并不是所有的背包都那么明显,有时候需要进行转化。

例题 1:

例题 1

题目大意:

有 $n$ 个物品,每个物品要么花费 A 代价 $t_i$ 元,要么花费 B 代价 $w_i$ 元,使最后的总代价 A 比 B 大,求最小的总代价 A。

数据范围:

  • $n\in[1,100]$
  • $t_i\in\{1,2\}$
  • $w_i\in[1,100]$

做法:

我们可以设 $f_{i,j,k}$ 为前 $i$ 个物品,花费了 A 代价 $j$ 元,B 代价 $k$ 元的可行性,可行为 $1$,不可行为 $0$,边界条件为 $f_{0,0,0}=1$。
推出转移方程:
$$f_{i,j,k}=\begin{cases}1&j\ge t_i\land f_{i-1,j-t_i,k}\&k\ge w_i\land f_{i-1,j,k-w_i}\&otherwise\end{cases}$$ 最后倒序循环枚举 $f_{n,i,j}$,找到第一个可行的直接输出 $i$ 即可。
总体时空复杂度为 $O(n(\sum t_i)^2)$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define repe(i,x,y,z) for(int i=x;i<=y;i+=z)
#define eper(i,x,y,z) for(int i=x;i>=y;i-=z)
#define fst ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define inl inline
#define INF LLONG_MAX
using namespace std;
const int N=105,T=205;
int n,t[N],w[N],sum;
bool f[N][T][T];
signed main(){
	fst;
	cin>>n;
	rep(i,1,n){
		cin>>t[i]>>w[i];
		sum+=t[i];
	}
	f[0][0][0]=1;
	rep(i,1,n)
		rep(j,0,sum)
			rep(k,0,sum){
				if(j>=t[i]) f[i][j][k]|=f[i-1][j-t[i]][k];
				if(k>=w[i]) f[i][j][k]|=f[i-1][j][k-w[i]];
			}
	rep(i,0,sum)
		rep(j,0,i)
			if(f[n][i][j]){
				cout<<i;
				return 0;
			}
	return 0;
}

P2239 [NOIP2014 普及组] 螺旋矩阵 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-04-01 15:52:05

P2239 [NOIP2014 普及组] 螺旋矩阵 讲解

题目
数学题。

$50$ 分做法:

先暴力做出这个 $n\times n$ 的矩阵,最后直接输出。
时间复杂度为 $O(n^2)$,$n\in[1,30000]$,超时,考虑优化。

$O(n)$ 做法:

我们找出了所有的 $a_{i,j}$,但只输出了一个,浪费了大量时间。
参考以下蛇形方阵: $$\begin{bmatrix}1&2&3&4&5\&17&18&19&6\ &24&25&20&7\ &23&22&21&8\ &12&11&10&9\end{bmatrix}$$ 直接推式子有些难推,我们分类讨论:

  • $i=1,j\in(1,n):a_{i,j}=j$
  • $i\in(1,n),j=n:a_{i,j}=n+i-1$
  • $i=n,j\in(1,n):a_{i,j}=3n-j-1$
  • $i\in(1,n),j=1:a_{i,j}=4n-i-2$

不是以上情况怎么办呢? 我们给矩阵缩小一下:
$$\begin{bmatrix}1&2&3\\8&9&4\&6&5\end{bmatrix}$$ 可以看到,中间的九个元素都减少了 $16$,也就是 $4(n-1)$。
那么设这个函数为 $f(n,i,j)$,最后一种情况为:

  • $i\in(1,n),j\in(1,n):f(n-2,i-1,j-1)+4(n-1)$ 总体复杂度为 $O(n)$,可以通过。
    代码:
#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 0x2a2a2a2a
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int n,i,j;
inl int find(int n,int i,int j){
	if(i==1) return j;
	if(j==n) return n+i-1;
	if(i==n) return (n<<1)+n-j-1;
	if(j==1) return (n<<2)-i-2;
	return find(n-2,i-1,j-1)+((n-1)<<2);
}
signed main(){
	FAST;
	cin>>n>>i>>j;
	cout<<find(n,i,j);
	return 0;
}

$O(1)$ 做法:

虽说已经可以通过此题,但如果 $n$ 的规模再大一些,达到 $n\in[1,10^9]$,那就必须 $O(1)$ 解决了。
我们找一个更大的矩阵: $$\begin{bmatrix}1&2&3&4&5&6&7&8\8&29&30&31&32&33&34&9\&48&49&50&51&52&35&10\&47&60&61&62&53&36&11\&46&59&64&63&54&37&12\&45&58&57&56&55&38&13\&44&43&42&41&40&39&14\&21&20&19&18&17&16&15\end{bmatrix}$$ 一轮缩小,减少了 $4\times(8-1)=28$。
二轮缩小,减少了 $4\times(6-1)=20$。
三轮缩小,减少了 $4\times(4-1)=12$。
$b=\{28,20,12\}$,翻转一下变成 $b'=\{12,20,28\}$,可以看出这是一个等差数列。
很明显会缩小 $x=min(i,j,n-i+1,n-j+1)$ 次。
$b'$ 的末项是 $4(n-1)$,公差是 $8$,首项就是 $4(n-1)-8(x-2)$,得 $b_{sum}$: $$b_{sum}=\frac{(4(n-1)-8(x-2)+4(n-1))\times(x-1)}{2}$$ 总体复杂度为 $O(1)$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 0x2a2a2a2a
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int n,i,j,x,y; 
signed main(){
	FAST;
	cin>>n>>i>>j;
	x=min(min(i,n-i+1),min(j,n-j+1));
	y=((((n-((x-2)<<1)-1)<<2)+((n-1)<<2))*(x-1))>>1;
	i-=x-1;
	j-=x-1;
	n-=((x-1)<<1);
	if(i==1) cout<<y+j;
	else if(n-j+1==1) cout<<y+n+i-1;
	else if(n-i+1==1) cout<<y+(n<<1)+n-j-1;
	else cout<<y+(n<<2)-i-2;
	return 0;
}

P2669 [NOIP2015 普及组] 金币 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-04-02 16:19:55

P2669 [NOIP2015 普及组] 金币 讲解

题目

$O(n)$ 做法:

两层循环,第一层的 $i$ 表示第 $i$ 个周期(连续 $i$ 天),第二层的 $j$ 表示周期内在 $[1,i]$ 天,然后每次给 $ans$ 加上 $i$,最后输出即可。

$O(\sqrt n)$ 做法:

一层循环,考虑到第二层循环加了 $i$ 次 $i$,所以第二层循环可以去掉,在第一层循环加上 $i\times i$,时间复杂度是 $O(\sqrt n)$ 的。
虽说这已经很快了,但我们还可以用 $O(1)$ 做法完成。

$O(1)$ 做法:

推理平方差公式:

我们想一想,有没有一个公式快速的求出 $\sum_{i=1}^xi^2$ 呢?
$$\begin{aligned}\sum_{i=1}^ni^3-(i-1)^3&=n^3-(n-1)^3+(n-1)^3-(n-2)^3+\dots+1^3-0^3\&=n^3\end{aligned}$$ 以上等式成立,继续往下:
$$\sum_{i=1}^ni^3-(i-1)^3=n^3$$ $$\sum_{i=1}^ni^3-(i^3-3i^2+3i-1)=n^3$$ $$\sum_{i=1}^ni^3-i^3+3i^2-3i+1=n^3$$ $$(\sum_{i=1}^n3i^2-3i)+n=n^3$$ $$3(\sum_{i=1}^ni^2-i)+n=n^3$$ $$3(\sum_{i=1}^ni^2)-3(\sum_{i=1}^ni)+n=n^3$$ $$3(\sum_{i=1}^ni^2)-\frac{3n(n+1)}{2}+n=n^3$$ 我们设 $\sum_{i=1}^ni^2$ 为 $S(n)$,则:
$$3S(n)-\frac{3n(n+1)}{2}+n=n^3$$ $$6S(n)-3n(n+1)+2n=2n^3$$ $$6S(n)-3n^2-3n+2n=2n^3$$ $$6S(n)-3n^2-n=2n^3$$ $$6S(n)=2n^3+3n^2+n$$ $$S(n)=\frac{2n^3+3n^2+n}{6}$$ (标准的写法是 $\frac{n(n+1)(2n+1)}{6}$)

解一元二次方程:

方程 $n=\frac{x(x+1)}{2}$,变式: $$n=\frac{x^2+x}{2}$$ $$n=\frac{x^2}{2}+\frac{x}{2}$$ $$n=\frac{1}{2}x^2+\frac{1}{2}x$$ $$\frac{1}{2}x^2+\frac{1}{2}x-n=0$$ 得 $a=\frac{1}{2},b=\frac{1}{2},c=-n,\Delta=b^2-4ac$。
最后答案就是 $ans=\frac{-b+\sqrt\Delta}{2a}$,最后答案就是 $(n-\frac{ans(ans+1)}{2})\times(ans+1)+\frac{ans(ans+1)(2ans+1)}{6}$。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF 0x2a2a2a2a
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
int n,ans,num,ans2;
double a,b,c,delta; 
signed main(){
	FAST;
	cin>>n;
	a=b=0.5;
	c=-n;
	delta=b*b-4*a*c;
	ans=(-b+sqrt(delta))\/2\/a;
	num=ans*(ans+1)\/2;
	ans2=(2*ans*ans*ans+3*ans*ans+ans)\/6;
	n-=num;
	cout<<n*(ans+1)+ans2;
	return 0;
}

招财猫专题比赛题解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-04-16 18:40:32

招财猫专题比赛题解

A 招财猫做数学题

考察内容:欧几里得定理。
题目简述:求 $\gcd(a,b,a+b)$ 和 $lcm(a,b,a+b)$。

众所周知: $$\gcd(a,b)=\gcd(b,a\bmod b)$$ $$lcm(a,b)=a\times b\div\gcd(a,b)$$ $$\gcd(a,b,c)=\gcd(a,\gcd(b,c))$$ $$lcm(a,b,c)=lcm(a,lcm(b,c))$$ 那么我们得到: $$\begin{aligned}lcm(a,b,c)&=lcm(a,b\times c\div \gcd(b,c))\&=a\times b\times c\div\gcd(b,c)\div\gcd(a,b\times c\div\gcd(b,c))\end{aligned}$$ 结束。

代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF LLONG_MAX
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
int a,b; 
signed main(){
	FAST;
	cin>>a>>b;
	cout<<__gcd(a+b,__gcd(a,b))<<' '<<a*b\/__gcd(a,b)*(a+b)\/__gcd(a*b\/__gcd(a,b),a+b);
    return 0;
}

B 最大数字

考察内容:字符串,高精度。
题意简述:求字符串中最大的数字所在的位置,最大数字 $\le 10^{100}$。

高精度比较:

  • 先比较位数,位数多的数就大。
  • 从最高位开始,比较每一位,直到比较出大小。

这就是一个模拟题。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF LLONG_MAX
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
string s,mx="0",x;
int mxa,l=0;
inl bool compare(string a,string b){
	if(a.size()<b.size()) return 1;
	if(a.size()>b.size()) return 0;
	rep(i,0,a.size()-1){
		if(a[i]<b[i]) return 1;
		if(a[i]>b[i]) return 0;
	}
	return 0;
}
signed main(){
	FAST;
	cin>>s;
	rep(i,0,s.size()-1)
		if(isdigit(s[i])){
			if(!l) l=i+1;
			x+=s[i];
		}else if(x!=""){
			if(compare(mx,x)){
				mx=x;
				mxa=l;
			}
			x="";
			l=0;
		}
	if(x!=""&&compare(mx,x)) cout<<l;
	else cout<<mxa;
    return 0;
}

C emo 的招财猫

考察内容:01 背包。
题目简述:求 $c_{1\dots n}$,使 $c_i\in\{0,1\}$,$\sum_{i=1}^nc_i\times v_i\le m$,$\sum_{i=1}^nw_i\times c_i$ 最大。

01 背包板子题。当然我知道有人不会背包
第一层循环正序枚举 $i$(意思是选前 $i$ 个物品),第二层循环枚举 $j$(意思是背包的容量为 $j$),那么对于每一个 $i,j$,第 $i$ 个物品可以选,可以不选,那么可以得到:
$$f_{i,j}=\min(f_{i-1,j-v_i}+w_i,f_{i-1,j})$$ 由于每一个 $f_{i,j}$ 只与 $f_{i-1,j}$ 有关,所以第一维可以压掉,但 $j$ 要倒序枚举,因为 $f_{i,j}$ 只跟 $f_{i-1,j-x}(x\ge 0)$ 有关。

代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF LLONG_MAX
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
const int N=6e3+5,M=8e4+5;
int n,m,v[N],w[N],f[M],ans;
signed main(){
	FAST;
	cin>>n>>m;
	rep(i,1,n) cin>>v[i]>>w[i];
	rep(i,1,n)
		per(j,m,v[i]) f[j]=max(f[j],f[j-v[i]]+w[i]);
	rep(i,0,m) ans=max(ans,f[i]);
	cout<<ans;
    return 0;
}

D [WFZ7789] 最近的质数(简易版)

逆天数据

考察内容:埃氏筛。
题目大意:$n$ 次询问,每次给出一个 $k$,求 $\min_{x\in prime}(|x-k|)$。

首先进行埃氏筛(他的思路就是筛去每一个质数的倍数),他的复杂度约为 $O(n\log_2\log_2n)$。
筛到多少合适呢?
我们可以筛到 $\max_{i=1}^n(a_i)+25$,因为一个数是质数的几率是 $\frac{1}{log_2x}$,于是多开 $25$。
然后对于每一个 $k$,我们循环查找即可,不需二分,还是因为质数的密度较大。

结束。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF LLONG_MAX
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
const int N=5e6+5,M=1e5+5;
int cnt,n,k[M],mx;
bool vis[N];
inl void init(int x){
	rep(i,2,x)
		if(!vis[i])
			rep(j,2,x\/i) vis[j*i]=1;
}
signed main(){
	FAST;
	cin>>n;
	rep(i,1,n){
		cin>>k[i];
		mx=max(mx,k[i]);
	}
	init(mx+25);
	rep(i,1,n){
		if(!vis[k[i]]) cout<<0<<endl;
		else{
			int mx=0,mn=0;
			while(vis[mx+k[i]]) ++mx;
			while(vis[k[i]-mn]) ++mn;
			cout<<min(mx,mn)<<endl;
		}
	}
    return 0;
}

E 草方块

考察:递推。
题目简述:给定一个 $n\times m$ 的格子图,求里面的长方形和正方形数量。

$O(n^4)$ 做法:

暴力枚举矩形的左上坐标和右下坐标,复杂度为 $O(n^4)$。

$O(n^2)$ 做法:

暴力枚举矩形的长和宽,复杂度为 $O(n^2)$。

$O(n)$ 做法:

我们考虑到,取一个矩形是在格子图的长和宽上取一条线段,他们的可能性分别有 $\displaystyle\frac{n(n+1)}{2}$ 和 $\displaystyle\frac{m(m+1)}{2}$ 种,那么一共就有 $\displaystyle(\frac{n(n+1)}{2})(\frac{m(m+1)}{2})$ 种。
然后我们只需枚举正方形的边长即可。

$O(1)$ 做法:

正方形的个数是 $\displaystyle\sum_{i=1}^{\min(n,m)}(n-i+1)(m-i+1)$,我们要把它变成 $O(1)$ 的。 $$\begin{aligned}\sum_{i=1}^{\min(n,m)}(n-i+1)(m-i+1)&=\sum_{i=1}^{\min(n,m)}(\min(n,m)-i+1)(\min(n,m)-i+1)+(\min(n,m)-i+1)(\max(n,m)-\min(n,m))\&=\sum_{i=1}^{\min(n,m)}i^2+i(\max(n,m)-\min(n,m))\&=(\sum_{i=1}^{\min(n,m)}i^2)+(\max(n,m)-\min(n,m))(\sum_{i=1}^{\min(n,m)}i)\&=\frac{n(n+1)(2n+1)}{6}+\frac{n(n+1)}{2}(\max(n,m)-\min(n,m))\end{aligned}$$ (平方和公式见 P2669 [NOIP2015 普及组] 金币 讲解)。

代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF LLONG_MAX
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
const int MOD=1e9+7;
int n,m,inv2,ans,ans1,inv3;
inl int poww(int a,int b){
	int num=a,ans=1;
	while(b){
		if(b&1) (ans*=num)%=MOD;
		(num*=num)%=MOD;
		b>>=1;
	}
	return ans;
}
signed main(){
	FAST;
	cin>>n>>m;
	if(n<m) swap(n,m);
	n%=MOD;
	m%=MOD;
	inv2=poww(2,MOD-2);
	inv3=poww(3,MOD-2);
	ans=((n*(n+1)%MOD*inv2%MOD*m%MOD*(m+1)%MOD*inv2%MOD)%MOD+MOD)%MOD;
\/\/	cout<<ans<<endl;
	ans1=((m*(m+1)%MOD*(m<<1|1)%MOD*inv2%MOD*inv3%MOD+(n-m)*m%MOD*(m+1)%MOD*inv2%MOD)%MOD+MOD)%MOD;
	ans=((ans-ans1)%MOD+MOD)%MOD;
	cout<<ans1<<' '<<ans<<endl;
    return 0;
}
\/\/5*3+4*2+3*1
\/\/3*3+2*2+1*1
\/\/2*(3+2+1)

F 招财猫

考察:线段树。
题意简述:给你一个序列 $a_{1,2,\dots,n}$,有以下三种操作:

  1. 将 $[l,r]$ 区间内的数增加 $v$。
  2. 将 $[l,r]$ 区间内的数减少 $v$。
  3. 求 $[l,r]$ 区间内的数的和。

$n^2$ 暴力想必都会写,那么如何降低复杂度?
我们应引入一个高级数据结构:线段树。

线段树是一个像下面这张照片的数据结构:
每一个节点存的是他所对应的 $[l,r]$ 区间的和(其他的也可以),实现方法请参考 线段树讲解
请系统学习过树状数组的同学可以尝试参考 树状数组做线段树,并尝试解此题。
代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF LLONG_MAX
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
const int N=1e5+5;
int n,q,a[N],opt,l,r,v;
struct trees{
	int l,r,lan,sum;
}t[N<<2];
inl void pushup(int x){
	t[x].sum=t[x<<1].sum+t[x<<1|1].sum;
}
inl void build(int x,int l,int r){
	t[x].l=l;
	t[x].r=r;
	if(l==r){
		t[x].sum=a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(x<<1,l,mid);
	build(x<<1|1,mid+1,r);
	pushup(x);
}
inl void pushdown(int x){
	if(t[x].lan==0) return;
	t[x<<1].lan+=t[x].lan;
	t[x<<1|1].lan+=t[x].lan;
	t[x<<1].sum+=(t[x<<1].r-t[x<<1].l+1)*t[x].lan;
	t[x<<1|1].sum+=(t[x<<1|1].r-t[x<<1|1].l+1)*t[x].lan;
	t[x].lan=0;
}
inl void add(int x,int l,int r,int v){
	if(l<=t[x].l&&t[x].r<=r){
		t[x].sum+=(t[x].r-t[x].l+1)*v;
		t[x].lan+=v;
		return;
	}
	pushdown(x);
	int mid=(t[x].l+t[x].r)>>1;
	if(l<=mid) add(x<<1,l,r,v);
	if(r>mid) add(x<<1|1,l,r,v);
	pushup(x);
}
inl int getsum(int x,int l,int r){
	if(l<=t[x].l&&t[x].r<=r) return t[x].sum;
	pushdown(x);
	int mid=(t[x].l+t[x].r)>>1,tmp=0;
	if(l<=mid) tmp+=getsum(x<<1,l,r);
	if(r>mid) tmp+=getsum(x<<1|1,l,r);
	return tmp;
}
signed main(){
	FAST;
	cin>>n>>q;
	rep(i,1,n) cin>>a[i];
	build(1,1,n);
	rep(i,1,q){
		cin>>opt;
		if(opt==1){
			cin>>l>>r>>v;
			add(1,l,r,v);
		}else if(opt==2){
			cin>>l>>r>>v;
			add(1,l,r,-v);
		}else{
			cin>>l>>r;
			cout<<getsum(1,l,r)<<endl;
		}
	}
    return 0;
}

G 招财猫要送红包了!

考察:单调队列,动态规划。
题意简述:在二维平面上,你要去 $n$ 个亲戚家送红包,你的家在 $(x_0,y_0)$,第 $i$ 个亲戚的家在 $(x_i,y_i)$。
你需要从 $1$ 到 $n$ 的顺序去送红包,但你每次最多拿 $k$ 个红包,求走过的最短距离。

$O(nk)$ 做法:

设 $f_i$ 是送完 $i$ 家亲戚,再回到原点所走过的最短距离,$\text{dist}(x,y)$ 是从 $x$ 点到 $y$ 点的距离,$sum_i$ 是距离的前缀和,即 $\displaystyle \sum_{k=1}^{i-1}\text{dist}(k,k+1)$,那么: $$f_i=\min_{j=i-k}^{i-1}(f_j+\text{dist}(0,j+1)+sum_i-sum_{j+1}+\text{dist}(i,0))$$ 时间复杂度为 $O(nk)$,超时。

$O(n\log_2n)$ 做法:

很明显,$f_i$ 只跟 $f_j-sum_{j+1}+\text{dist}(0,j+1)\ (i-k\le j\le i-1)$ 有关,那么这就是区间最小值,我们可以线段树维护,请读者自行编码。

复杂度为 $O(n\log_2n)$,得分 $80$ 分。

$O(n)$ 做法:

我们又想到可以用双端队列来维护滑动窗口,这样复杂度就可以降到 $O(n)$。

代码:

#include<bits\/stdc++.h>
#define m(i,j) sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]))
#define ans(i) f[i]+m(i+1,0)-sum[i+1]
#define int long long
#define inl inline
#define INF LLONG_MAX
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
deque<int>d;
const int MAX=5e5+5;
int n,k,x[MAX],y[MAX];
double sum[MAX],f[MAX];
void que(int l){
	while(!d.empty()&&d.front()+k<=l) d.pop_front();
	while(!d.empty()&&ans(d.back())>ans(l)) d.pop_back();
	d.push_back(l);
	return; 
}
signed main(){
	FAST;
	cin>>n>>k;
	rep(i,0,n) cin>>x[i]>>y[i];
	rep(i,1,n) sum[i]=sum[i-1]+m(i,i-1);
	d.push_back(0);
	rep(i,1,n){
		f[i]=ans(d.front())+m(i,0)+sum[i];
		que(i);
	}
	printf("%.6lf",f[n]);
	return 0;
}	

T446079 十年灯 讲解

本文章由 WyOJ Shojo 从洛谷专栏拉取,原发布时间为 2024-04-25 17:50:55

T446079 十年灯 讲解

题目
考察内容:图论。

Task 1:

简述:在可以交换任意一个点的两条出边的权值无数次的情况下,求从 $1$ 到 $n$ 的最短路。

由于没有负环(负权都没有),每个点必然只到达一次,那么每次走的那条边必是那个点的出边中权值最小的那个边,那么可以把每个点的所有出边都设为最小值,这样结果是不变的。
最后跑一遍 dijkstra 就行了。

Task 2:

简述:在可以交换任意一个点的两条的权值无数次的情况下,求从 $1$ 到 $n$ 的最短路。

我们很容易发现不管两个边有没有共点,我们都有办法使两条边交换。那么我们最后走的必是最短的 $k$ 条边。

代码:

#include<bits\/stdc++.h>
#define int long long
#define inl inline
#define INF LLONG_MAX
#define rep(i,x,y) for(int i=x;i<=y;++i)
#define rer(i,x,y,cmp) for(int i=x;i<=y&&cmp;++i)
#define per(i,x,y) for(int i=x;i>=y;--i)
#define FAST ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define endl '\n'
using namespace std;
const int N=4e5+5;
struct edge{
	int to,w;
	inl friend bool operator<(edge x,edge y){
		return x.w>y.w;
	}
};
int id,n,m,u[N],v[N],w[N],f[N],mnc[N];
vector<edge>g[N];
priority_queue<edge>pq;
priority_queue<int,vector<int>,greater<int> >p; 
bool vis[N];
inl void dijkstra(){
	f[1]=0;
	pq.push({1,0});
	while(!pq.empty()){
		int u=pq.top().to;
		pq.pop();
		if(vis[u]) continue;
		vis[u]=1;
		for(auto x:g[u]){
			int v=x.to,w=x.w;
			if(f[v]>f[u]+w){
				f[v]=f[u]+w;
				if(!vis[v]) pq.push({v,f[v]});
			}
		}
	}
}
signed main(){
	FAST;
	memset(f,0x3f,sizeof(f));
	memset(mnc,0x3f,sizeof(mnc));
	cin>>id>>n>>m;
	rep(i,1,m){
		cin>>u[i]>>v[i]>>w[i];
		if(id==1) mnc[u[i]]=min(mnc[u[i]],w[i]);
		if(id==2) p.push(w[i]);
	}
	rep(i,1,m){
		if(id==1) g[u[i]].push_back({v[i],mnc[u[i]]});
		if(id==2) g[u[i]].push_back({v[i],1});
	}
	dijkstra();
	if(id==1) cout<<f[n];
	else{
		int sum=0;
		rep(i,1,f[n]){
			sum+=p.top();
			p.pop();
		}
		cout<<sum;
	}
    return 0;
}
共 127 篇博客