1. 程式人生 > 其它 >CodeForces - 466C Number of Ways(推公式/dp)

CodeForces - 466C Number of Ways(推公式/dp)

技術標籤:動態規劃推公式

題目連結:點選檢視

題目大意:給出一個長度為 n 的數列,現在要求出滿足條件的 ( i , j ) 的匹配數量,滿足:\sum_{k=1}^{i-1}a_k=\sum_{k=i}^{j}a_k=\sum_{k=j+1}^{n}a_k

題目分析:訓練時推的公式,簡單說一下吧,維護字首和 sum,則確定兩個斷點 ( i , j ) 後可以確定下來三個區間:

  1. sum[ i - 1 ]
  2. sum[ j] - sum[ i - 1 ]
  3. sum[ n ] - sum[ j ]

因為需要三段區間的權值和連等,根據等式的傳遞性,我們可以讓前兩項相等,再讓後兩項相等,最後不難推出三項連等,建立等式:

  1. 前兩項相等:sum[ i - 1 ] = sum[ j ] - sum[ i - 1 ],推得 2 * sum[ j ] = sum[ i - 1 ] * 4(兩側同時乘以 2,下面要用)
  2. 後兩項相等:sum[ j ] - sum[ i - 1 ] = sum[ n ] - sum[ j ],推得 2 * sum[ j ] = sum[ n ] - sum[ i - 1 ]

利用 2 * sum[ j ] 當做中間量,不難看出當確定下來 i 後,只有滿足sum[ n ] - sum[ i - 1 ] =sum[ i - 1 ] * 4 時才存在 j 與其匹配,此時只需要查詢一下區間 [ i , n ] 中有多少個 j 與其匹配即可


另一種方法,是將其轉換成一個很經典的 dp 模型,這裡拿一下 PTA 乙級題目的題面:

字串APPAPT中包含了兩個單詞PAT,其中第一個PAT是第 2 位(P

),第 4 位(A),第 6 位(T);第二個PAT是第 3 位(P),第 4 位(A),第 6 位(T)。

現給定字串,問一共可以形成多少個PAT

比較顯然的是,假設 sum 為 n 個數之和,( i , j ) 將整個序列分成的三段大小分別都是 sum / 3(設 sum % 3 == 0),所以第一個斷點的位置也就是字首和等於 sum / 3 的位置,同理第二個斷點的位置也就是字首和等於 sum / 3 * 2 的位置,如此一來就轉換成了上面那個非常經典的動態規劃模型:

給定 n 個位置,問一共可以形成多少個 ( sum/3 的位置,sum/3*2 的位置)

如此一來這個題目就可以繼續擴充套件了,諸如匹配 ( i , j , k ) 三元對的斷點將數列四等分劃分這樣的

妙啊

程式碼:
推公式

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
  
typedef long long LL;
  
typedef unsigned long long ull;
  
const int inf=0x3f3f3f3f;
  
const int N=1e6+100;

LL sum[N];

map<LL,int>mp;

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.ans.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		LL num;
		scanf("%lld",&num);
		sum[i]=sum[i-1]+num;
	}
	LL ans=0;
	for(int i=n-1;i>1;i--)//列舉i  
	{
		mp[sum[i]*2]++;
		if(4*sum[i-1]==sum[n]+sum[i-1]) 
			ans+=mp[sum[n]+sum[i-1]];
	}
	printf("%lld\n",ans);












    return 0;
}

dp

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<string>
#include<ctime>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<stack>
#include<climits>
#include<queue>
#include<map>
#include<set>
#include<sstream>
#include<cassert>
#include<bitset>
using namespace std;
  
typedef long long LL;
  
typedef unsigned long long ull;
  
const int inf=0x3f3f3f3f;
  
const int N=1e6+100;

LL sum[N],dp[N][2];

int main()
{
#ifndef ONLINE_JUDGE
//  freopen("data.ans.txt","r",stdin);
//  freopen("data.out.txt","w",stdout);
#endif
//  ios::sync_with_stdio(false);
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		LL num;
		scanf("%lld",&num);
		sum[i]=sum[i-1]+num;
	}
	if(sum[n]%3!=0)
		return 0*puts("0");
	LL mark=sum[n]/3;
	for(int i=1;i<=n;i++)
	{
		for(int j=0;j<2;j++)
			dp[i][j]=dp[i-1][j];
		if(i!=n)
		{
			if(sum[i]==mark)
				dp[i][0]++;
			if(sum[i]==mark*2)
				dp[i][1]+=dp[i-1][0];
		}
	}
	printf("%lld\n",dp[n][1]);










    return 0;
}