SSF信息社团寒假训练题目整理(二)

Hint:可以点击右下角的目录符号快速跳转到指定位置

上接:SSF信息社团寒假训练题目整理(一)
下接:SSF信息社团寒假训练题目整理(三)

Day 1(2.1)

Contest Link

1081E

Link

(S_1=sumlimits_{j=1}^{2i-1} x_j=p^2)(S_2=sumlimits_{j=1}^{2i} x_j=q^2)(1le ile n/2)),则 (x_{2i}=S_2-S_1=q^2-p^2=(q+p)(q-p))。于是我们可以 (mathcal O(sqrt{x_{2i}})) 枚举 (p+q)(p-q),贪心地使 (p) 尽量小(这样做是为了使 (x_{2i-1}) 尽量小,见后面 (x_{2i-1}) 的式子),令 (S_2') 为上一个计算出的 (S_2),就能计算出 (x_{2i-1}=S_1-S_2')。按照这种方法将所有奇数位的 (x_i) 计算出即可,时间复杂度 (mathcal O(n+sumlimits_{i=1}^{n/2}sqrt{x_{2i}}))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
typedef long long ll;
const int N=1e5;
ll a[N+10],sum[N+10];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=2;i<=n;i+=2) scanf("%lld",&a[i]);
	ll lasty=0;
	for(int i=1;i<=n;i+=2)
	{
		ll x=0,y=0;
		bool flag=0;
		
		for(int j=sqrt(a[i+1]);j;j--)//y-x
		{
			if(a[i+1]%j) continue;
			if(a[i+1]/j%2!=j%2) continue;
//			printf("j:%d
",j);
			y=(a[i+1]/j+j)/2;
			x=(a[i+1]/j-j)/2;
//			if(x==y) continue;
			if(x*x-lasty*lasty<=0||y<lasty) continue;
			flag=1;
			break;
		}
//		puts("----"); 
		if(!flag)
		{
			printf("No");
			return 0;
		}
		a[i]=x*x-lasty*lasty;
		lasty=y;
	}
	printf("Yes
");
	for(int i=1;i<=n;i++) printf("%lld ",a[i]);
	return 0;
}

1077F1

Link

题意:(n) 个数,选 (m) 个,问如何选才能使这 (m) 个数和最大且任意连续 (k) 个数中都至少有一个被选中。

(f(i,j)) 表示前 (i) 个数选择 (j) 个数,第 (i) 个数必选的最优答案,不难得出 (f(i,j)=max{f(x,j-1)+a_i}),其中 (max{0,i-k}le x<i),初始状态为 (f(0,0)=0),其余为 (-infty),目标状态为 (maxlimits_{i=n-k+1}^n{f(i,m)})

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long
const int N=200;
int dp[N+10][N+10],a[N+10];
signed main()
{
	int n,k,m;
	scanf("%lld %lld %lld",&n,&k,&m);
//	if(m<(n/k))
//	{
//		printf("-1");
//		return 0;
//	}
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
//	dp[1][0]=0;dp[1][1]=a[1];
//	for(int i=1;i<=k;i++) dp[i][1]=a[i];
	for(int i=1;i<=n;i++)
	{
		for(int j=(i-1)/k+1;j<=min(i,m);j++)
		{
//			printf("(%d, %d) ",i,j);
			for(int x=max(0ll,i-k);x<i;x++)
				dp[i][j]=max(dp[x][j-1]+a[i],dp[i][j]);
		}
//		putchar('
');
	}
//	for(int i=1;i<=n;i++)
//	{
//		for(int j=1;j<=m;j++) printf("%lld ",dp[i][j]);
//		putchar('
');
//	}
	int ans=0;
	for(int i=n-k+1;i<=n;i++) 
	{
//		printf("%lld ",dp[i][m]);
		ans=max(ans,dp[i][m]);
	}
	printf("%lld",ans==0?-1:ans);
	return 0;
}

1076E

Link

离线处理,维护一个以深度为下标的树状数组,每遍历到一个点 (x),以将 (x) 为根的操作所加的权值都加到树状数组的下标 (min{ ext{dep}_x+d_i,D}) 中,其中 ( ext{dep}_x) 表示 (x) 的深度,(D) 表示树的最大深度。查询 ( ext{dep}_xsim D) 的权值和,存到 (x) 的答案中。随后遍历所有儿子,进行上面的操作,后代遍历结束后将 (x) 加到树状数组中的权值取消。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;
const int N=3e5,M=N*2;
int head[N+10],ver[M+10],nxt[M+10],tot=0;
void add(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
int dep[N+10],d;
void dfs1(int x,int fa,int dd)
{
//	printf("(%d, %d)
",x,fa);
	d=max(d,dd);
	dep[x]=dd;
	for(int i=head[x];i;i=nxt[i])
	{
		int y=ver[i];
		if(y==fa) continue;
		dfs1(y,x,dd+1);
	}
}
typedef long long ll;
ll c[N+10];
void modify(int x,int dd) {for(;x<=d;x+=x&-x)c[x]+=dd;}
ll query(int x)
{
	ll ans=0;
	for(;x;x-=x&-x)ans+=c[x];
	return ans;
}
vector<pair<int,int> > v[N+10];//first:depth, second:value
ll ans[N+10];
void dfs(int x,int fa)
{
//	printf("x:%d
",x);
	for(int i=0;(unsigned)i<v[x].size();i++)
	{ 
//		printf("(%d, %d) ",v[x][i].first,v[x][i].second);
		modify(v[x][i].first,v[x][i].second);
	}
//	putchar('
');
	ans[x]=query(d)-query(dep[x]-1);
	for(int i=head[x];i;i=nxt[i])
	{
		int y=ver[i];
		if(y==fa) continue;
		dfs(y,x);
	}
	for(int i=0;(unsigned)i<v[x].size();i++)
		modify(v[x][i].first,-v[x][i].second); 
}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	dfs1(1,-1,1);
	int q;
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		int vi,di,xi;
		scanf("%d%d%d",&vi,&di,&xi);
//For each query Vasya adds value xi to each vertex from di-subtree of vi.
		v[vi].push_back(make_pair(min(d,dep[vi]+di),xi));
	}
//	for(int i=1;i<=n;i++)
//	{
//		printf("%d:",i);
//		for(int j=0;(unsigned)j<v[i].size();j++) 
//			printf("(%d, %d) ",v[i][j].first,v[i][j].second);
//		putchar('
');
//	}
	dfs(1,-1); 
	for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
	return 0;
}

Day 7(2.2)

Contest Link

687C

Link

(f(i,j,l)) 表示前 (i) 个硬币组成面值 (j),面值 (l) 能否被拼出,若可以为 (1),反之为 (0)。可以得到转移:

[f(i,j,l)=f(i-1,j,l)operatorname{or}f(i-1,j-c_i,l)operatorname{or}f(i-1,j-c_i,l-c_i) ]

其中 (operatorname{or}) 表示逻辑或。计算答案时,统计 (f(n,k,i))(0le ile k))即可。由于每次的转移的第一维只与 (i-1)(i) 有关,所以可以使用滚动数组。时间复杂度 (mathcal O(nk^2))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=500;
int a[N+10],dp[N+10][N+10];
int main()
{
	int n,k;
	scanf("%d %d",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	dp[0][0]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=k;j>=a[i];j--)
		{
			for(int l=0;l<=k;l++)
			{
				dp[j][l]|=dp[j-a[i]][l];
				if(l>=a[i]) dp[j][l]|=dp[j-a[i]][l-a[i]]; 
			}
		}
	}
	int ans=0;
	for(int i=0;i<=k;i++) ans+=dp[k][i];
	printf("%d
",ans);
	for(int i=0;i<=k;i++) if(dp[k][i]) printf("%d ",i);
	return 0; 
}

685B

Link

预处理出以每个点为根的子树的大小及最大真子树的根编号,之后 dfs 一遍计算每个节点的答案。dfs 过程中,先处理儿子的答案,若最大真子树的重心不满足重心条件(以树的重心为根时,所有子树的大小都不超过整棵树大小的一半)就不断往上跳直到满足条件。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
using namespace std;
inline void read(int &x)
{
	x=0; int f=1;
	char c=getchar();
	while(c<'0' || c>'9')
	{
		if(c=='-') f=-1;
		c=getchar(); 
	}
	while(c>='0' && c<='9')
	{
		x=(x<<1)+(x<<3)+(c^48);
		c=getchar();
	}
	x*=f;
}
const int N=300000;
int head[N+10],ver[N*2+10],nxt[N*2+10],tot=0;
void add(int x,int y)
{
	ver[++tot]=y;
	nxt[tot]=head[x];
	head[x]=tot;
}
int sz[N+10],son[N+10],fa[N+10];
void dfs1(int x,int f)
{
//	printf("x:%d, fa:%d
",x,f);
	sz[x]=1;
	fa[x]=f;
	for(int i=head[x];i;i=nxt[i])
	{
		int y=ver[i];
		if(y==f) continue;
		dfs1(y,x);
		sz[x]+=sz[y];
		if(sz[y]>sz[son[x]]) son[x]=y;
	}
}
int ans[N+10];
void dfs(int x)
{
//	printf("dfs(x):%d
",x);
	ans[x]=x;
	if(sz[x]==1) return;
	//sz[x]-sz[k] <= sz[x]/2
	//2(sz[x]-sz[k]) <= sz[x]
	for(int i=head[x];i;i=nxt[i])
	{
		int y=ver[i];
		if(y==fa[x]) continue;
		dfs(y);
	}
	int k=ans[son[x]];
//	printf("x=%d, k:%d
",x,k); 
//	system("pause");
	if(sz[son[x]]*2>sz[x])
	{
		while(2*(sz[x]-sz[k])>sz[x]) 
		{
//			printf("k:%d
",k);
			k=fa[k];
		}
		ans[x]=k;
	} 
} 
int main()
{
	int n,q;
	read(n);read(q);
	for(int i=2;i<=n;i++)
	{
		int x;
		scanf("%d",&x);
		add(x,i);
		add(i,x);
	}
	dfs1(1,0);
//	puts("HELLO");
	dfs(1);
//	puts("HELLO");
	for(int i=1;i<=q;i++)
	{
		int x;
		scanf("%d",&x);
		printf("%d
",ans[x]); 
	}
	return 0;
}

682D

Link

(f(i,j,l,0/1)) 表示 (S) 串前 (i) 位,(T) 串前 (j) 位,取 (l) 个字串,当前一位取不取(取为 (1),反之为 (0))的最优答案,不难得出,当 (S_i ot=T_j) 时:

[f(i,j,l,0)=max{f(i-1,j,l,0),f(i-1,j,l,1),f(i,j-1,l,0),f(i,j-1,l,1)} ]

(S_i=T_j) 时:

[f(i,j,l,0)=max{f(i-1,j,l,0),f(i-1,j,l,1),f(i,j-1,l,0),f(i,j-1,l,1)}\ f(i,j,l,1)=max{f(i-1,j-1,l-1,0),f(i-1,j-1,l-1,1),f(i-1,j-1,l,1)}+1 ]

时间复杂度 (O(nmk))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int N=1000;
int dp[N+10][N+10][20][2];
char a[N+10],b[N+10];
int main()
{
	int n,m,l;
	scanf("%d%d%d%s%s",&n,&m,&l,a+1,b+1);
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			for(int k=1;k<=l;k++)
			{
				if(a[i]==b[j])
				dp[i][j][k][1]=max(max(dp[i-1][j-1][k-1][0],dp[i-1][j-1][k-1][1]),dp[i-1][j-1][k][1])+1;
				dp[i][j][k][0]=max(max(dp[i-1][j][k][0],dp[i-1][j][k][1]),
				max(dp[i][j-1][k][0],dp[i][j-1][k][1]));
			}
		}
	} 
	printf("%d",max(dp[n][m][l][0],dp[n][m][l][1]));
	return 0;
}

Day 8(2.3)

Contest Link

650B

Link

题意:有 (n) 张照片排列成一个环,第 (1) 张在第 (n) 张的右边,第 (2) 张在第 (1) 张的右边,以此类推。有 (T) 秒的时间去看这些照片,从第 (1) 张开始看,每看一张照片需要 (1) 秒,向左或向右滑动一张照片需要 (a) 秒,有些照片是不正的,需要把他们翻转过来再看,翻转需要 (b) 秒。如果翻到一张照片且这张照片没看过,就必须把他看完;若看过,则跳过,继续下一张。现在问在这 (T) 秒内共能看多少张不同的照片。

注意到 (1le nle 5 imes 10^5),猜测标算的时间复杂度是 (mathcal O(nlog n))。这种复杂度如果不是线段树等 (log) 级别的数据结构和倍增,就是二分。手玩样例可知,最优解一定形如下面的方案:

即从 (1) 开始向左边或者右边走,然后折返回来,向着与刚才相反的方向走。只向右边走或者只向左边走的情况可以看作是其相反的方向走了 (0) 个点。于是可以想到一种二分方案:枚举第一次(红色)走到的最远的点(图中为点 (4)),然后二分第二次(橙色)能够走到最远的点。因为无法确定一开始是向左走还是向右走较优,所以要二分两边,一次计算一开始向左边走的答案,一次计算一开始向右边走的答案。至于二分过程中如何计算代价,可以通过一个前缀和和一个后缀和来实现。还有一个小 trick,因为 (1) 要被两边的路径同时经过,所以先把 (1) 的代价从 (T) 中减掉,如果此时 (T<0),直接输出 (0) 即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=5e5;
int pre[N+10],suf[N+10];
char s[N+10];
int main()
{
	int n,a,b,T;
	scanf("%d%d%d%d%s",&n,&a,&b,&T,s+1);
	// It takes a seconds to swipe from photo to adjacent.
	// It takes b second to change orientation of the photo.
	// He spends 1 second to notice all details in it.
	if(s[1]=='w') T-=b;
	T-=1;
	// printf("T:%d
",T);
	if(T<0)
	{
		printf("0");
		return 0;
	}
	for(int i=2;i<=n;i++)
		pre[i]=pre[i-1]+1+(s[i]=='w')*b+a;
	for(int i=n;i>1;i--)
		suf[i]=suf[i+1]+1+(s[i]=='w')*b+a;

	int ans=0;
	// for(int i=1;i<=n;i++)
	// {
	// 	printf("pre[%d]:%d, suf[%d]:%d
",i,pre[i],i,suf[i]);
	// }

	// 1 -> x -> 1 -> y
	// right      left
	// int ans=0;
	for(int i=1;i<=n;i++)
	{
		int tmp=pre[i]+(i-1)*a;
		if(tmp>T) break;
		// printf("tmp:%d
",tmp);
		int l=1,r=n,p=-1;
		while(l<=r)
		{
			int mid=(l+r)/2;
			if(suf[mid]<=T-tmp)
			{
				r=mid-1;
				p=mid;
			}
			else l=mid+1;
		}
		if(p==-1) continue;
		// printf("i=%d, p=%d
",i,p);
		ans=max(ans,i+(n-p+1));
		// printf("ans:%d
",ans);
		// printf("p:%d
",p);
	}
	bool flag=0;
	for(int i=1;;i--)
	{
		if(i==0) i=n;
		if(i==1&&flag) break;
		flag=1;
		// printf("i:%d
",i);
		int tmp=suf[i]+(i>1?(n-i+1)*a:0);
		// printf("tmp:%d
",tmp);
		if(tmp>T) break;
		int l=1,r=n,p=-1;
		while(l<=r)
		{
			int mid=(l+r)/2;
			if(pre[mid]<=T-tmp)
			{
				p=mid;
				l=mid+1;
			}
			else r=mid-1;
		}
		// printf("i:%d, p:%d
",i,p);
		// if(i==2) break;
		if(p==-1) continue;
		ans=max(ans,(i==1?0:n-i+1)+p);
	}
	printf("%d",min(ans,n));
	return 0;
}

660D

Link

注意到没有三点共线,所以可以 (mathcal O(n^2)) 枚举两点构成的直线,在 STL map 中存储直线的斜率和两点之间距离,直线斜率、距离相同的两个点对可以构成一个平行四边形。形式化地,若两个点分别为 ((x_1,y_1),(x_2,y_2)),map 定义为 map<pair<double,double>,int>,那么就将 (dfrac{y_2-y_1}{x_2-x_1})(sqrt{(y_2-y_1)^2+(x_2-x_1)^2}) 绑成一个 pair,这个 pair 所对应在 map 中的 value 加一。特别地,若 (x_2-x_1=0),将这两个点的斜率记为一个不会在 (x_2 ot=x_1) 的情况中出现的极大值即可,代码中取的是 (998244353998244353998244353)。为了避免 double 爆精度,两点之间的“距离”实际上存的是实际距离的平方,即 ((y_2-y_1)^2+(x_2-x_1)^2)。统计时,对于每个出现过的斜率与距离组成的点对 ((a,b)),若他们出现过 (c) 次,那么它们给答案带来的贡献就是 (mathrm C_c^2=dfrac{c(c-1)}2)。然而这样统计我们就会把每个平行四边形都统计两遍,所以最后输出是答案要除以 (2)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
using namespace std;
const int N=2000;
typedef long long ll; 
struct node
{
	ll x,y;
	node(){}
	node(ll xx,ll yy) {x=xx;y=yy;}
}a[N+10];
double dis(node x,node y) {return (x.x-y.x)*(x.x-y.x)+(x.y-y.y)*(x.y-y.y);}
double k(node x,node y) 
{
	if(y.x-x.x==0) return 998244353998244353998244353;
	return (y.y-x.y)/1.0/(y.x-x.x);
}
//struct Frac
//{
//	
//};
int main()
{
	map<pair<double,double>,int> book;
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
	{
		scanf("%lld %lld",&a[i].x,&a[i].y);
		a[i].x*=1000;
//		a[i].y*=1000000;
	}
	for(int i=1;i<=n;i++) 
		for(int j=i+1;j<=n;j++)
		{
//			if(a[i].x==a[j].x) continue;
			book[make_pair(k(a[i],a[j]),dis(a[i],a[j]))]++;
		}
	long long ans=0;
	for(map<pair<double,double>,int>::iterator it=book.begin();
	it!=book.end();it++)
	{
//		printf("k:%.3lf,dis:%.3lf,cnt:%d
",
//		it->first.first,it->first.second,it->second);
		int cnt=it->second;
//		printf("CNT*(CNT-1)/2:%d
",cnt*(cnt-1)/2);
		ans+=cnt*(cnt-1)/2;
//		printf("ans:%lld
",ans);
	}
	printf("%lld",ans/2ll);
	return 0;
}

653C

Link

将所有不符合条件的数((i) 为偶数且 (t_ile t_{i+1}) 或者 (i) 为奇数且 (t_ige t_{i=1}))拎出来,若有解,则数量一定不超过 (4),若数量超过了 (4),说明无解,直接输出 (0) 即可。随后,把这些点暴力与剩下所有点都尝试交换一遍,若能使所有不符合条件的数都符合条件且交换的数周围符合条件的数在交换之后仍然符合条件,累加答案。

至于代码中为什么判断的是 cnt>8,是因为我考试的时候脑抽把不符合条件的数周围都插进了 set 中,就导致数量多了一倍左右。虽然说这样也能做,但是巨麻烦。如果想看正经做法的代码,请参考这篇题解

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
#include<map>
#include<set>
using namespace std;
const int N=150000;
int a[N+10],n,cnt;
map<pair<int,int>,bool> book; 
set<int> s;
bool check(int j)
{
	set<int> ss=s;
	ss.insert(j);
	if(j>1) ss.insert(j-1);
	if(j<n) ss.insert(j+1);
	for(set<int>::iterator it=ss.begin();it!=ss.end();it++)
	{
		int i=*it;
		if(i==n) continue;
		if(i%2 && a[i]>=a[i+1]) return false;
		if(i%2==0 && a[i]<=a[i+1]) return false;
	}
	return true;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
//	a[0]=2147483647;
//	vector<int> v;
	for(int i=1;i<n;i++) 
	{
		if(i&1)
		{
//			if(a[i]>=min(a[i-1],a[i+1]))
//			{ 
//				v.push_back(i);
//				s.insert(i);
//				if(i>1) s.insert(i-1);
//				if(i<n) s.insert(i+1);
//			}	
			if(a[i]>=a[i+1])
			{
//				v.push_back(i);
				s.insert(i);
				if(i>1) s.insert(i-1);
				if(i<n) s.insert(i+1);
			}
		}
		else
		{
//			if(a[i]<=max(a[i-1],a[i+1]))
//			{ 
//				v.push_back(i);
//				s.insert(i);
//				if(i>1) s.insert(i-1);
//				if(i<n) s.insert(i+1);
//			} 
			if(a[i]<=a[i+1])
			{
//				v.push_back(i);
				s.insert(i);
				if(i>1) s.insert(i-1);
				if(i<n) s.insert(i+1);
			}
		}
	}
	cnt=s.size();
//	printf("cnt:%d
",cnt);
	if(cnt>8)
	{
		printf("0");
		return 0;
	}
	long long ans=0;
	for(set<int>::iterator it=s.begin();it!=s.end();it++)
	{
		int i=*it;
		for(int j=1;j<=n;j++)
		{
			if(j==i) continue;
			if(book[make_pair(min(j,i),max(j,i))]) continue;
			swap(a[j],a[i]);
			if(check(j)) ans++; 
			swap(a[j],a[i]);
			book[make_pair(min(j,i),max(j,i))]=1;
		}
	}
	printf("%lld",ans);
	return 0;
}

Day 9(2.4)

Contest Link

633C

Link

建一棵 Trie 树,将所有单词转换成小写后倒着插入,同时在字符串结尾所对应的节点编号打一个标记,记录这个单词是第几个给出的。令 (f(i)) 表示密码串 (s)(s_0sim s_i) 能否通过反转后的单词连接得到,若可以则值为 (1),反之为 (0)。令 (f(-1)=1) 可以得到转移:(f(j)=f(i-1)operatorname{and} g(i,j)quad(j>i))(operatorname{and}) 表示逻辑与),其中 (g(i,j)) 表示 (s_{i}sim s_j) 是否是给出的单词,若是则值为 (1),反之为 (0)(g(i,j)) 的实现即为上面所说的字典树,常规查询即可。值域路径输出,可以在 dp 的同时记录一个路径数组 (pre),类型为 ( exttt{pair<int, int>})( exttt{pre.first}) 存储此状态由哪里转移过来(密码串下标),( exttt{pre.second}) 记录通过哪个字符串转移过来(单词下标),前者转移过程中直接记录,后者在 Trie 树查询的过程中记录即可。总的时间复杂度为 (mathcal O(n^2 + sumlimits_{i=1}^m mid t_imid))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
const int Sigma=1000000;
int trie[Sigma+10][26],tot=0;
bool dp[Sigma+10];int e[Sigma+10];
void ins(string s,int pos)
{
	int len=s.length(),p=0;
	for(int i=len-1;~i;i--)
	{
		char c=s[i];
		if(c>='A' && c<='Z') c+=32;
		if(!trie[p][c-'a']) trie[p][c-'a']=++tot;
		p=trie[p][c-'a'];
	}
	e[p]=pos; 
}
string s,t[Sigma+10];
pair<int,int> pre[Sigma+10];
//first:上一个的坐标,second:哪个串 

int st[Sigma+10],top=0;
int main()
{
//	cout<<'a'-'A';
	ios::sync_with_stdio(false);
	int n;
	cin>>n>>s;
	int m;
	cin>>m;
	for(int i=1;i<=m;i++) 
	{
		cin>>t[i];
		ins(t[i],i);
	}
//	cout<<"tot:"<<tot<<endl;
//	dp[0]=1;
	for(int i=0;i<n;i++)
	{
		if(i==0 || dp[i-1])
		{
			int p=0;
			for(int j=i;j<n;j++)
			{
				char c=s[j];
				if(!trie[p][c-'a']) break;
				p=trie[p][c-'a'];
				if(e[p])
				{
					dp[j]=1;
					pre[j]=make_pair(i-1,e[p]);
//					cout<<t[e[p]]<<"---"<<endl;
				}
			}
		}
	}
	int y=n-1;
	while(1)
	{
		st[++top]=pre[y].second;
		y=pre[y].first;
		if(y<0) break;
	}
	for(int i=top;i;i--) cout<<t[st[i]]<<" "; 
}

630E

Link

(X) 轴的角度观察,可以发现当 (y_2-y_1) 为偶数时,随着坐标 (x) 的递增,在这个坐标上的正六边形数量按照多 ( o)( o)( o) 少……的规律变化,反之数量不变。若为前者,则变化过程中多的数量为 (lfloor (y_2-y_1)/2 floor+1),少的数量为 (lfloor (y_2-y_1)/2 floor);若为后者,则数量都为 (lfloor (y_2-y_1)/2 floor +1)。由于正六边形可以移动,又因为 (x_2-x_1) 一定是偶数,让边界的正六边形数量都是较多的即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
#define int long long
signed main()
{
	int X1,Y1,X2,Y2;
	scanf("%lld %lld %lld %lld",&X1,&Y1,&X2,&Y2);
	int m,l;
	if(abs(Y2-Y1)%2==1)
	{
		m=(Y2-Y1)/2+1;
		l=(Y2-Y1)/2+1;
	}
	else
	{
		m=(Y2-Y1)/2+1;
		l=(Y2-Y1)/2;
	}
	int ans=0;
	ans+=m;
	ans+=((X2-X1)/2)*(m+l);
	printf("%lld",ans);
	return 0; 
}

622D

Link

首先猜想 (s=0)。如果这个结论成立,那么对于 (forall iin[1,n)),都要满足 (mid d_i+i-nmid=0)(d_i+i=n)。若 (n) 为奇数,将 (1,3,5,cdots,n)(2,4,6,cdots,n-1) 分成两组观察,发现同组相邻的两个数 (d_i) 都相差 (2),于是可以有这样一种构造方案:(1,3,cdots,n-2,n,n-2,cdots,3,1,2,4,cdots,n-3,n-1,n-1,n-3,cdots,4,2,n),这样可以保证所有满足 (i<n)(i) 都有 (d_i+i=n)。类似地,若 (n) 为偶数,可以这样构造:(1,3,cdots,n-3,n-1,n-1,n-3cdots,3,1,2,4,cdots,n-2,n,n-2,cdots4,2,n)。这样的构造方案能够使 (s=0)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
int main()
{
	int n;
	scanf("%d",&n);
	if(n%2)
	{
		for(int i=1;i<=n;i+=2) printf("%d ",i);
		for(int i=n-2;i>=1;i-=2) printf("%d ",i);
		for(int i=2;i<n;i+=2) printf("%d ",i);
		for(int i=n-1;i>=2;i-=2) printf("%d ",i);
		printf("%d ",n);
	}
	else
	{
		for(int i=1;i<n;i+=2) printf("%d ",i);
		for(int i=n-1;i>=1;i-=2) printf("%d ",i);
		for(int i=2;i<n;i+=2) printf("%d ",i);
		printf("%d ",n);
		for(int i=n-2;i>=2;i-=2) printf("%d ",i);
		printf("%d",n);
	}
	return 0; 
}

Day 10(2.5)

Contest Link

613A

Link

((x_0,y_0)) 在图形中能到达的最远距离为 (a),最近距离为 (b),那么答案为 ((a^2-b^2)pi)。可以看出,能够取到 (a)(b) 的点,要么是题目中给出的点 ((x_i,y_i)),要么是 ((x_0,y_0)) 向给出的边 ((x_i,y_i)(x_j,y_j)) 作垂所得的垂足。如果将 ((x_i,y_i),(x_j,y_j)) 所在直线 和 垂足与 ((x_0,y_0)) 所在直线 分别看作一次函数 (f(x)=kx+b)(g(x)=k'x+b'),那么垂足坐标就是 (f(x))(g(x)) 图像的交点。(f(x)) 解析式很容易求,(g(x)) 则不是很容易,需要一个 Lemma

Lemma:(f(x)=kx+b) 的图像与 (g(x)=k'x+b') 的图像垂直,那么 (k'=-dfrac{1}{k})

Prove: 

( an(alpha+90 °) =dfrac{sin(alpha+90°)}{cos(alpha+90°)} =dfrac{sin90°cosalpha + cos90°sinalpha}{cos90°cosalpha-sin90°sinalpha}=dfrac{cosalpha}{-sinalpha}=-dfrac{1}{ analpha}\ecause k= analpha,k'= an(alpha+90°)\ herefore k'=-dfrac{1}{k})

然后就可以算垂足坐标:

注意,计算垂足坐标时有三种特殊情况:

  1. (x_1=x_2),此时直线 (AB) 不是一次函数,(D) 的坐标为 ((x_1,y_0))
  2. (y_1=y_2),此时直线 (CD) 不是一次函数,(D) 的坐标为 ((x_0,y_1))
  3. (D) 在线段 (AB) 延长线上,此时这个点不合法,需要跳过。

总的时间复杂度是 (mathcal O(n))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
//#define double long double
const double PI=3.14159265358979323846265433832795028841971;	
double X0,Y0;
pair<double,double> cz(double X1,double Y1,double X2,double Y2)
{
	if(X1==X2) return make_pair(X1,Y0);
	if(Y1==Y2) return make_pair(X0,Y1);
	double k=(Y2-Y1)/(X2-X1),k0=-(1/k);

	double b=Y1-k*X1,b0=Y0-k0*X0;
	return make_pair((b0-b)/(k-k0),(b0-b)/(k-k0)*k0+b0);
}
double dis(double X1,double Y1,double X2,double Y2)
{return (X1-X2)*(X1-X2) + (Y1-Y2)*(Y1-Y2);}
double x[100010],y[100010];
int main()
{
//	X0=-3;Y0=3;
//	printf("%Lf,%Lf",cz(-3,2,5,3).first,cz(-3,2,5,3).second);
//	cout<<cz(0,,4,2).first<<" "<<cz(0,2,4,2).second;
//cout<<acos(-1)<<endl; 
	int n;
	scanf("%d%lf%lf",&n,&X0,&Y0);
	for(int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]);
	double Min=1e18,Max=0;
	for(int i=1;i<=n;i++)
	{
		Min=min(Min,dis(X0,Y0,x[i],y[i]));
		Max=max(Max,dis(X0,Y0,x[i],y[i]));
	}
	for(int i=1;i<=n;i++)
	{
		int pre=(i==1?n:i-1);
		pair<double,double> point=cz(x[pre],y[pre],x[i],y[i]);
		if(point.first<min(x[pre],x[i]) || point.first>max(x[pre],x[i])) continue;
		if(point.second<min(y[pre],y[i]) || point.second>max(y[pre],y[i])) continue;
		Min=min(Min,dis(X0,Y0,point.first,point.second));
		Max=max(Max,dis(X0,Y0,point.first,point.second)); 
	}
	printf("%.10lf",(Max-Min)*PI);
	return 0;
}

613B

Link

({a_i}) 从小到大排序,使用 (mathcal O(n)) 的代价使 (i)(0)(n) 枚举后面有几个 Skills 升到满级,此时答案的第一部分 (c_f imes i) 已定。同时,(mathcal O(log A)) 二分最小值,来计算答案的第二部分 (min{a_i} imes c_m),这个二分的 check(mid) 函数需要再使用二分来判断有多少个 (a_i) 满足 (a_ile mid),时间复杂度为 (mathcal O(log n))。总的时间复杂度 (mathcal O(nlog Alog n))

三年 OI 一场空,不开 long long 见祖宗。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
#define int long long
const int N=1e5;
struct node
{
	int val,pos;
	node(int v,int p){val=v;pos=p;}
	node(){}
	bool operator<(const node &x)const{return val<x.val;}
}a[N+10];
int pre[N+10],suf[N+10];
//前缀和     后缀和
int n,A,cf,cm,m;
bool check(int v,int mm,int ed)
{
	int l=1,r=ed,ans=0;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(a[mid].val<=v)
		{
			ans=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	return mm-(v*ans-pre[ans])>=0;
}
int sol(int x)// x: max_cnt
{
	int mm=m-(A*x-suf[n-x+1]);
	if(mm<0) return -1;
	int l=a[1].val,r=A,ans=0;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(check(mid,mm,n-x))
		{
			ans=mid;
			l=mid+1;
		}
		else r=mid-1;
	}
	return ans;
}// return: min
int Ans[N+10];
signed main()
{
	scanf("%lld%lld%lld%lld%lld",&n,&A,&cf,&cm,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i].val);
		a[i].pos=i;
	}
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++) pre[i]=pre[i-1]+a[i].val;
	for(int i=n;i>=1;i--) suf[i]=suf[i+1]+a[i].val;
	int ans=0,vf=0,vm=0;
	for(int i=0;i<=n;i++)
	{//max_cnt
		int tmp=sol(i);
		if(tmp==-1) break;
		if(i*cf+tmp*cm>ans)
		{
			ans=i*cf+tmp*cm;
			vf=i;
			vm=tmp;
		}
	} 
	for(int i=1;i<=vf;i++) a[n-i+1].val=A;
	for(int i=1;i<=n;i++)
	{
		if(a[i].val>=vm) break;
		a[i].val=vm;
	}
	for(int i=1;i<=n;i++) Ans[a[i].pos]=a[i].val;
	printf("%lld
",ans);
	for(int i=1;i<=n;i++) printf("%lld ",Ans[i]);
	return 0;
}
/*
The number of skills that a character has perfected (i.e., such that ai = A), multiplied by coefficient cf.
The minimum skill level among all skills (min ai), multiplied by coefficient cm.

ans = cnt_max * cf + min * cm
*/ 

607B

Link

(f(l,r)) 表示将 (l)(r) 都清除的最小次数,不难看出一个通用的转移:(f(l,r)=maxlimits_{k=l}^{r-1}{f(l,k)+f(k+1,j)}),在此基础上,若 (a_l=a_r),还可以有 (f(l,r)=f(l+1,r-1)),代表这在区间 ([l+1,r-1]) 中删除到只剩一个回文串,此时这个回文串左边接上 (a_l),右边接上 (a_r),仍是回文串,所以可以不计代价,还是 (f(l+1,r-1))。注意需要特判 (r-l+1=1)(r-l+1=2) 的边界情况。时间复杂度为 (mathcal O(n^3))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=500;
int a[N+10],dp[N+10][N+10];
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) dp[i][i]=1;
	for(int i=1;i<n;i++) dp[i][i+1]=1+(a[i]!=a[i+1]);
	for(int l=3;l<=n;l++)
	{
		for(int i=1;i+l-1<=n;i++)
		{
			int j=i+l-1;
			dp[i][j]=0x7fffffff;
			for(int k=i;k<j;k++) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
			if(a[i]==a[j]) dp[i][j]=min(dp[i][j],dp[i+1][j-1]);
		}
	}
	printf("%d",dp[1][n]);
	return 0;
}

Day 11(2.6)

Contest Link

582B

Link

(T<n),直接 (mathcal O(T^2n^2)) 暴力计算即可。

(Tge n),计算前 (n) 个区间计算最长不下降子序列,长度记为 (L)。统计前 (n) 个数中出现数量最多的一个数,记为 (j),其数量记为 (c_j),在前 (n) 个区间中取出 (a_ile j) 所组成的数列的最长不下降子序列并作为最终答案的第一部分;在剩余 (T-n) 个区间中将 (j) 全都取出,作为答案的第二部分;在最后一个区间中,将最开始计算的最长不下降子序列中 (>j) 的数在最后一个区间中找出,作为答案的第三个部分。这样计算能使得答案最优,为 (L+(T-n)c_j),时间复杂度为 (mathcal O(n^4))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100,M=300;
int a[N*N+10],cnt[M+10],dp[N*N+10];
int main()
{
	int n,t,mx=0;
	scanf("%d %d",&n,&t);
	for(int i=1;i<=n;i++) 
	{
		scanf("%d",&a[i]);
		cnt[a[i]]++;
		mx=max(mx,cnt[a[i]]);
	}
	if(t<=n)
	{
		for(int i=n+1;i<=n*t;i++)
			a[i]=a[i-n];
		for(int i=1;i<=n*t;i++)
		{
			dp[i]=1;
			for(int j=1;j<i;j++)
				if(a[j]<=a[i])
					dp[i]=max(dp[i],dp[j]+1);
		}
		int ans=0;
		for(int i=1;i<=n*t;i++) ans=max(ans,dp[i]);
		printf("%d",ans);
	}
	else
	{
		for(int i=n+1;i<=n*n;i++)
			a[i]=a[i-n];
		for(int i=1;i<=n*n;i++)
		{
			dp[i]=1;
			for(int j=1;j<i;j++)
				if(a[j]<=a[i])
					dp[i]=max(dp[i],dp[j]+1);
		}
		int ans=0;
		for(int i=1;i<=n*n;i++) ans=max(ans,dp[i]);
		printf("%d",ans+(t-n)*mx);
	}
	return 0;
}

597C

Link

(f(i,j)) 表示以第 (i) 个数为结尾、长度为 (j) 的上升子序列的数量,则有转移 (f(i,j)=sum f(l,j-1)quad(1le l< i ext{ 且 }a_i=a_l))。如果暴力转移是 (mathcal O(n^2k)) 的复杂度,不能通过此题,于是考虑使用权值线段树优化。线段树以 (a_i) 为下标,存储 (f(i,j-1)),那么 (f(i,j)) 的转移实际就是一个区间求和的操作,每次转移完成后同时将 (f(i,j-1)) 放到权值线段树里即可。时间复杂度 (mathcal O(nklog n))。为实现方便,实际写的是树状数组。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
using namespace std;
#define int long long
const int N=1e5;
int m,n,k;
int c[N+10],a[N+10];
void modify(int x,int d){for(;x<=m;x+=x&-x)c[x]+=d;}
int query(int x)
{
	int ans=0;
	for(;x;x-=x&-x)ans+=c[x];
	return ans;
}
int f[N+10],b[N+10],pre[N+10];
signed main() 
{
	scanf("%lld%lld",&n,&k);k++;
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	memcpy(b,a,sizeof(a));
	sort(b+1,b+n+1);
	m=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+n+1,a[i])-b;
	
	for(int i=1;i<=n;i++)
	{
		f[i]=1;
		modify(a[i],1);
	}
	for(int j=2;j<=k;j++)
	{
//		printf("j=%lld
",j);
//		for(int i=1;i<=n;i++) printf("%lld ",f[i]);
//		putchar('
');
		memcpy(pre,f,sizeof(f));
		memset(f,0,sizeof(f));
		memset(c,0,sizeof(c));
		for(int i=1;i<=n;i++)
		{
			f[i]=query(a[i]-1);
			modify(a[i],pre[i]);
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++) ans+=f[i];
	printf("%lld",ans);
	return 0;
}

599D

Link

容易推出,若 (m<n),那么一个 (n imes m) 的矩形共包含 (sumlimits_{i=0}^{m-1} (n-i)(m-i)) 个正方形。这个式子直接做肯定不可行,于是推一波

[egin{align*}sumlimits_{i=0}^{m-1}(n-i)(m-i) &=sumlimits_{i=0}^{m-1}nm-i(n+m)+i^2\ &=sumlimits_{i=0}^{m-1} nm-sumlimits_{i=0}^{m-1}i(n+m)+sumlimits_{i=0}^{m-1} i^2\&=nm^2-dfrac{m(m-1)}{2}(n+m)+dfrac{m(m-1)(2m-1)}{6}end{align*} ]

(f(m,n)=nm^2-dfrac{m(m-1)}{2}(n+m)+dfrac{m(m-1)(2m-1)}{6},;f'(x)=f(x,x)),将 (f'(x)) 放到 GeoGebra 里生成图像,发现 (f'(x))(g(x)=x^{2.5}) 的函数图像基本吻合,所以可以有这样一种做法:从 (1)(x) 枚举 (m),同时二分 (n) 的值,如果可行就放到 vector 里。这样的做法时间复杂度最坏情况下是 (mathcal O(sqrt[2.5]{x}log x)),但实际实现时还会有许多常数优化,所以到不了这个上界。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;
#define int long long
int f(int m,int n) {return m*m*n - (m*(m-1)/2)*(m+n) + (m*(m-1)*(2*m-1)/6);}
signed main()
{
	vector<pair<int,int> > v;
	int s;
	scanf("%lld",&s);
	int pre=s;
	for(int i=1;f(i,i)<=s;i++)
	{
		int ans=0,l=i,r=pre;
		while(l<=r)
		{
			int mid=(l+r)/2;
			if(f(i,mid)>=s)
			{
				ans=mid;
				r=mid-1;
			}
			else l=mid+1;
		}
		if(f(i,ans)==s) v.push_back(make_pair(i,ans));
		pre=ans;
	}
	int cnt=v.size();
	if(v[cnt-1].first==v[cnt-1].second)
	{
		printf("%lld
",cnt*2-1);
		for(int i=0;i<cnt;i++) printf("%lld %lld
",v[i].first,v[i].second);
		for(int i=cnt-2;i>=0;i--) printf("%lld %lld
",v[i].second,v[i].first);
	}
	else
	{
		printf("%lld
",cnt*2); 
		for(int i=0;i<cnt;i++) printf("%lld %lld
",v[i].first,v[i].second);
		for(int i=cnt-1;i>=0;i--) printf("%lld %lld
",v[i].second,v[i].first);
	} 
}
原文地址:https://www.cnblogs.com/juruo-zzt/p/14354348.html