大师 洛谷p4933

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MrTinTin/article/details/83274607

题目背景

建筑大师最近在跟着数学大师ljt12138学数学,今天他学了等差数列,ljt12138决定给他留一道练习题。

题目描述

ljt12138首先建了n个特斯拉电磁塔,这些电塔排成一排,从左到右依次标号为1到n,第i个电塔的高度为h[i]。

建筑大师需要从中选出一些电塔,然后这些电塔就会缩到地下去。这时候,如果留在地上的电塔的高度,从左向右构成了一个等差数列,那么这个选择方案就会被认为是美观的。

建筑大师需要求出,一共有多少种美观的选择方案,答案模998244353。

注意,如果地上只留了一个或者两个电塔,那么这种方案也是美观的。地上没有电塔的方案被认为是不美观的。

输入输出格式

输入格式:

第一行一个正整数n。

第二行n个非负整数,第i个整数是第i个电塔的高度h[i]。

输出格式:

输出一个整数,表示美观的方案数模998244353的值。

输入输出样例

输入样例#1: 复制

8
13 14 6 20 27 34 34 41 

输出样例#1: 复制

50

输入样例#2: 复制

100
90 1004 171 99 1835 108 81 117 141 126 135 144 81 153 193 81 962 162 1493 171 1780 864 297 180 532 1781 189 1059 198 333 1593 824 207 1877 216 270 225 1131 336 1875 362 234 81 288 1550 243 463 1755 252 406 261 270 279 288 1393 261 1263 297 135 333 872 234 881 180 198 81 225 306 180 90 315 81 81 198 252 81 297 1336 1140 1238 81 198 297 661 81 1372 469 1132 81 126 324 333 342 81 351 481 279 1770 1225 549 

输出样例#2: 复制

11153

说明

我们用v表示最高的电塔高度。

对于前30%的数据,n<=20。

对于前60%的数据,n<=100,v<=2000。

对于另外20%的数据,所有电塔的高度构成一个等差数列。

对于100%的数据,n<=1000,v<=20000。

30分暴力:枚举子集后判断是否合法。

#include<bits/stdc++.h>
#define f(i,l,r) for(i=(l);i<=(r);i++)
using namespace std;
const int MAXN=1005,MOD=998244353;
int n,a[MAXN],b[MAXN];
inline bool check(int sta)
{
	int i;
	b[0]=0;
	f(i,0,n-1){
		if(sta&(1<<i)){
			b[++b[0]]=a[i];
		}
	}
	f(i,3,b[0]){
		if(b[i]-b[i-1]!=b[i-1]-b[i-2]) return false; 
	}
	return true;
}
int main()
{
	ios::sync_with_stdio(false);
	int i,j;
	int ans=0;
	cin>>n;
	f(i,0,n-1){
		cin>>a[i];
	}
	f(i,1,(1<<n)-1){
		if(check(i)) ans=(ans+1)%MOD;
	}
	cout<<ans<<endl;
	return 0;
}

60分暴力:边枚举边判断。

// luogu-judger-enable-o2
#include <bits/stdc++.h>

using namespace std;
int n,ans,a[1010],b[1010];
bool cheak(int x)
{
    if (x==1||x==2) return true;
    if (b[x]-b[x-1]==b[x-1]-b[x-2]) return true;
    return false;
}
void dfs(int x,int y)
{
    if (x>n) 
    {
        if (y!=0) ans++;
        ans%=998244353;
        return;
    }
    b[y+1]=a[x];
    if (cheak(y+1)) dfs(x+1,y+1);
    dfs(x+1,y);
}
int main()
{
    cin>>n;
    for (int i=1;i<=n;i++)
      cin>>a[i];
    dfs(1,0);
    cout<<ans;
}

60-80分dp:f[i][d]表示以i结尾的公差为d的数量,转移时枚举j<i,并且满足a[j]+d==a[i],公差可能为负。可特判原始数列为等差数列的情形。

#include<bits/stdc++.h>
#define f(i,l,r) for(i=(l);i<=(r);i++)
using namespace std;
const int MAXN=1005,MAXM=2005,MOD=998244353;
int n,a[MAXN];
int f[MAXN<<1][MAXM<<1];
int main()
{
	ios::sync_with_stdio(false);
	int i,j,d=0,k,flag=1,ans=0;
	int pd;
	cin>>n;
	f(i,1,n){
		cin>>a[i];
		if(i==2) pd=a[i]-a[i-1];
		else if(i>3) if(a[i]-a[i-1]!=pd) flag=0;
		d=max(d,a[i]);
	}
	if(flag){
		if(pd==0){
			cout<<(1<<n)-1<<endl;
			return 0;
		}
		f(i,1,n){
			int pos=i;
			for(j=1;pos+j<=n;j++){
				ans=(ans+(n-i)/j)%MOD;
			}
		}
		cout<<ans+n<<endl;
		return 0;
	}
	f(i,1,n){
		f(j,-d,d){
			f(k,1,i-1){
				if(a[k]+j==a[i]){
					f[i][j+d]+=f[k][j+d]+1;
	//				cout<<i<<" "<<j<<"GG"<<" "<<f[i][j+d]<<endl;
				}
			}
			ans=(ans+f[i][j+d])%MOD;
		}
		ans++;
	}
	cout<<ans<<endl;
	return 0;
}

100分dp:优化状态,

用f(i)表示以i结尾的,公差为d的等差数列有多少个,转移的时候枚举一个小于i的k,然后当h_k = h_i - dhk​=hi​−d的时候从f(k)f(k)转移到f(i)。

状态已经不可能再简化了,但是转移可以。

我们发现,转移相当于一个求和,对小于i的所有高度等于h_i - dhi​−d的位置的DP值求和。

我们可以维护一个数组g来记录这个和,这样转移就只有两行了。

f(i) = g(h_i - d)

g(h_i) = g(h_i) + f(i)

总复杂度是O(nv)

#include<bits/stdc++.h>
#define f(i,l,r) for(i=(l);i<=(r);i++)
using namespace std;
const int MAXN=1005,MAXM=20005,MOD=998244353;
int n,a[MAXN],f[MAXN],g[MAXM<<2],ans=0;
int main()
{
	ios::sync_with_stdio(false);
	int i,j,d=0;
	cin>>n;
	f(i,1,n){
		cin>>a[i];
		d=max(d,a[i]);
	}
	f(i,-d,d){
		memset(g,0,sizeof(g));
		f(j,1,n){
			f[j]=g[a[j]-i+d];
			g[a[j]+d]=(g[a[j]+d]+f[j]+1)%MOD;
			ans=(ans+f[j])%MOD;
	//		cout<<d<<" : "<<j<<" : "<<f[j]<<"GG"<<endl;
		}
	}
	cout<<(ans+n)%MOD<<endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/MrTinTin/article/details/83274607
今日推荐