1. 程式人生 > 其它 >【GDOI2022PJD1T4 小學生計數題】題解

【GDOI2022PJD1T4 小學生計數題】題解

D1T4 小學生計數題

題目

作為 GDOI 的組題人,小 Y 需要整理手中已有的題目,考慮它們的難度以及所考察的知識點,然後將它們組成數套題目。

小 Y 希望先能組出第一套題目,為了整套題目具有良好的區分度,在一套題目中:

  • 所有題目的難度需要能排成等差數列;(也就是說,若將所有題目按難度從小到大排序,那麼每相鄰兩題的難度的差相等,這個差叫做公差)
  • 每道題目的難度都是公差的倍數,公差不為 0;
  • 需要有不少於 \(L\) 道題,不多於 \(R\) 道題。

現在小 Y 手裡已經有了 \(m\) 道題目,其中難度為 \(a_i\) 的題有 \(c_i\)\((1 ≤ i ≤ n)\)

小 Y 希望能夠知道,他有多少種不同的方式能夠組出一套題目。

在這道題目中,我們認為兩種組題方式不同當且僅當 \(∃k(1 ≤ k ≤ m)\),使得一種方案包含第 \(k\) 道題而另一種方案不包含。

由於答案可能很大,輸出答案對 \(998244353\) 取模。

思路

先不考慮題目數量,我們把所有等差數列抽出來。滿足這些等差數列都不滿足一個完全包含另一個。

我們把這種等差數列定義為特殊等差數列

那麼此時對於任意一種難度 \(a_i\),它最多會被多少個特殊等差數列包含?

顯然,對於所有特殊等差數列,其公差必然互不相同

於是,這種難度 \(a_i\),它的因數個數必然小於100個(打個程式可以驗證),所以包含它的特殊等差數列個數必然小於100個。

然後我們就可以把所有特殊等差數列處理出來了。上面的時間複雜度是 \(O(n\log n)\)

我們把這些抽出來的等差數列分別處理,看看其有多少個符合要求的等差數列。

暴力的思想使列舉開頭和結尾,然後再暴力迴圈一遍求其方案,複雜度為 \(O(n^3)\),總複雜度為 \(O(n^4\log n)\)

顯然,最後一重暴力迴圈不必要,我們可以求個乘積字首和,然後套費馬小定理求逆元再求乘積,複雜度 \(O(n^3\log n)\)

然後我們發現逆元也可以再做一個字首和,然後再省掉一重迴圈,變為 \(O(n^2\log n\log n)\),還有一個 \(\log\) 是因為求逆元有個快速冪需要 \(\log\)。(我不知道線性求逆元在此題可不可行,感興趣可以試一試)

時間複雜度看起來會爆,然而,每個特殊等差數列。假如其個數多,那麼長度小。假如個數小,長度多。因此均攤

下來是 \(O(n\log n\log n)\) 的。

官方資料似乎不需要逆元字首和的優化,但民間資料卡了。

Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
inline 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 x*f;}
#define N 200010
//#define M
//#define mo
struct node
{
	int x, y; 
	bool operator <(const node &A) const
	{
		return y<A.y; 
	}
}p; 
int n, m, i, j, k, T;
vector<int>v[N]; 
int w[N], f[N], c[N], ans; 
priority_queue<node>q; 
//priority_queue<node>q; 

signed main()
{
	
	n=read(); 
	for(i=1; i<=n; ++i) w[i]=read(); 
	for(i=2; i<=n; ++i) 
	{
		f[i]=read(); c[f[i]]=1; 
//		printf("%lld\n", f[i]); 
		v[f[i]].push_back(i); 
	} 
	p.x=1; p.y=w[1]; q.push(p); ans=w[1]; 
	while(!q.empty())
	{
		p=q.top(); q.pop(); 
//		printf("%lld\n", p.x); 
		if(!c[k=p.x]) return printf("%lld\n", ans), 0; 
		for(i=0; i<v[k].size(); ++i) 
		{
			p.x=v[k][i]; p.y=w[v[k][i]]; 
			q.push(p); 
		}
		ans=min(ans, (int)q.top().y*(int)q.size() ); 
	}
	return 0;
}

總結

首先對於這道題,首先要想到的是製造特殊等差數列,因為很多情況就可以歸到一起來考慮了。

然後是對於題目中等差數列公差的特殊定義可以去思考,然後發現特殊等差數列個數受限於數的因數個數。

然後後面的優化就是憑經驗了。對於多次求逆元,可以考慮逆元字首和進行優化。