雅禮學習10.2
上午考試解題報告
各題狀況(爆零)
T1
想了10多分鐘,暴力只有一個極大複雜度的想法,顯然不可過,但還是寫了,然後就全TLE也是。。。意料之中
T2
暴力很好寫,但是錯誤理解了Tim給的部分分的意思:先給了一個\(a_i\le 10^9\),然後部分分裏面沒有提到\(a_i\)的情況,我就忽略了\(a_i\)的大小對答案統計的影響。。。
換句話說,我當時要是寫了離散化,就是\(43\)分到手。
T3
題目要求的輸出可以分成三個問題,第一個問題正確 的話可以得到這個點的\(25%\)的分數,前兩個問題正確的話可以得到該點\(50%\)的分數,三個問題全對可以獲得全部的分數
但是。。。因爲出題人的\(SPJ\)寫的有點問題,即使只求了第一個問題,後面的兩個問題也需要輸出點什麼來滿足\(SPJ\)的判斷方式。
出題人在給的\(PDF\)裏面點到了這個注意事項,但是是在整個\(PDF\)的最後一頁最後一行。。。
各題題目與考場代碼
T1
/*
* 考慮把所有的建築先都變成一樣高
* 然後往回推,暴力統計所有的情況
*/
#include <cstdio>
#include <algorithm>
inline int read()
{
int n=0,w=1;register char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar();
return n*w;
}
inline int min(int x,int y)
{return x<y?x:y;}
inline int max(int x,int y)
{return x>y?x:y;}
inline int abs(int x)
{return x<0?-x:x;}
const int N=1000001;
int n,c,ans=2147483647,h[N],a[N];
void dfs()
{
int emp=(a[1]-h[1])*(a[1]-h[1]);
for(int i=2;i<=n;++i)
{
emp+=abs(a[i]-a[i-1])*c;
emp+=(a[i]-h[i])*(a[i]-h[i]);
}
ans=min(ans,emp);
for(int i=1;i<=n;++i)
if(a[i]!=h[i])
{
--a[i];
dfs();
++a[i];
}
}
int main()
{
freopen("construct.in","r",stdin);
freopen("construct.out","w",stdout);
int maxn=0;
n=read(),c=read();
for(int i=1;i<=n;++i)
maxn=max(maxn,h[i]=read());
for(int i=1;i<=n;++i)
a[i]=maxn;
dfs();
printf("%d",ans);
fclose(stdin);fclose(stdout);
return 0;
}
T2
/*
* 23分應該可以模擬搞過去
* 蔬菜種類數不超過200的部分應該可以前綴和搞過去?
*/
#include <cstdio>
inline int read()
{
int n=0,w=1;register char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar();
return n*w;
}
inline int max(int x,int y)
{return x>y?x:y;}
const int N=201;
int r,c,q,ans,maxn,map[N][N],newmap[N][N][N];
inline void work()
{
for(int i=1;i<=r;++i)
for(int j=1;j<=c;++j)
{
for(int x=1;x<N;++x)
newmap[x][i][j]=newmap[x][i-1][j]+newmap[x][i][j-1]-newmap[x][i-1][j-1];
++newmap[map[i][j]][i][j];
}
int x,y,xx,yy,ans,emp;
while(q--)
{
ans=0;
x=read(),y=read(),xx=read(),yy=read();
for(int i=1;i<N;++i)
{
emp=newmap[i][xx][yy]-newmap[i][xx][y-1]-newmap[i][x-1][yy]+newmap[i][x-1][y-1];
ans+=emp*emp;
}
printf("%d\n",ans);
}
}
inline void solve()
{
int x,y,xx,yy,emp;
while(q--)
{
ans=0;
x=read(),y=read(),xx=read(),yy=read();
for(int k=1;k<=maxn;++k)
{
emp=0;
for(int i=x;i<=xx;++i)
for(int j=y;j<=yy;++j)
if(map[i][j]==k)
++emp;
ans+=emp*emp;
}
printf("%d\n",ans);
}
}
int main()
{
freopen("vegetable.in","r",stdin);
freopen("vegetable.out","w",stdout);
r=read(),c=read(),q=read();
for(int x,i=1;i<=r;++i)
for(int j=1;j<=c;++j)
{
map[i][j]=read();
maxn=max(maxn,map[i][j]);
}
if(q<=1000)
solve();
else
if(maxn<=200)
work();
fclose(stdin);fclose(stdout);
return 0;
}
T3
/*
* 第二個問題不會。。。
* 只能拿25%*20了
*/
#include <cstring>
#include <cstdio>
inline int read()
{
int n=0,w=1;register char c=getchar();
while(c<'0'||c>'9'){if(c=='-')w=-1;c=getchar();}
while(c>='0'&&c<='9')n=n*10+c-'0',c=getchar();
return n*w;
}
inline int min(int x,int y)
{return x<y?x:y;}
inline int max(int x,int y)
{return x>y?x:y;}
/*
const int N=300001;
struct Edge{
int v,nxt;
}edge[N<<1];
int n,tot,maxn,ans,head[N];//,ansedge[N],top,anspoint[4];
bool mark[N<<1];
inline void add(int u,int v)
{edge[++tot]=(Edge){v,head[u]};head[u]=tot;}
void dfs(int now,int step,int fa)
{
maxn=max(maxn,step);
for(int v,i=head[now];i;i=edge[i].nxt)
if((v=edge[i].v)!=fa && !mark[i])
dfs(v,step+1,now);
}
*/
const int N=3001;
int n,maxn,ans=2147483647;
bool map[N][N],vis[N];
void dfs(int now,int step,int fa)
{
vis[now]=true;
maxn=max(maxn,step);
for(int i=1;i<=n;++i)
if(map[now][i] && !vis[i])
dfs(i,step+1,now);
}
int main()
{
freopen("league1.in","r",stdin);
// freopen("league.out","w",stdout);
n=read();
for(int u,v,i=1;i<n;++i)
{
u=read(),v=read();
map[u][v]=map[v][u]=true;
}
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
{
if(!map[i][j])continue;
map[i][j]=map[j][i]=false;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
{
if(map[i][j])continue;
map[i][j]=map[j][i]=true;
maxn=0;
dfs(1,0,0);
for(int i=2;i<=n;++i)
if(!vis[i])
goto E;
for(int i=2;i<=n;++i)
{
dfs(i,0,0);
memset(vis,false,sizeof vis);
}
ans=min(ans,maxn);
E: map[i][j]=map[j][i]=false;
}
map[i][j]=map[j][i]=true;
}
/* for(int u,v,i=1;i<n;++i)
{
u=read(),v=read();
add(u,v),add(v,u);
}
for(int i=1;i<=tot;++i)
{
mark[i]=mark[i+1]=true;
for(int j=1;j<=n;++j)
for(int k=j+1;k<=n;++k)
{
add(j,k),
*/
/* for(int i=1;i<=tot;++++i)
{
mark[i]=true;
for(int j=1;j<=n;++j)
for(int k=j+1;k<=n;++k)
{
add(j,k),add(k,j);
maxn=0;
for(int l=1;l<=n;++l)
dfs(l,0,0);
if(ans>maxn)
{
ans=maxn;
ansedge[top=1]=i;
anspoint[0]=edge[i].v;
anspoint[1]=edge[i^1].v;
anspoint[2]=j;
anspoint[3]=k;
}
head[k]=edge[tot--].nxt;
head[j]=edge[tot--].nxt;
}
mark[i]=false;
}*/
printf("%d",ans);
fclose(stdin);fclose(stdout);
return 0;
}
正解思路及代碼
T1
記\(f_i\)表示考慮前\(i\)個建築,並且第\(i\)個建築的高度不變的答案,每次轉移的時候枚舉上一個不變的建築編號,中間的一段一定變成相同的高度,並且高度小於等於兩端的高度
假設從\(f_j\)轉移並且中間高度是\(t\),那麼\(f_i=\sum_{k=j+1}^{i-1}(t-h_k)^2+c(h[j]+h[i]-2t)\)
這樣中間的高度可以\(O(1)\)求二次函數的對稱軸
考慮優化轉移,因爲中間的高度小於兩端,所以最多有\(h_j\gt h_i\)的\(j\)能夠轉移,可以維護關於高度的單調棧,那麼有效的轉移次數就是\(O(N)\)
#include <bits/stdc++.h>
using std::pair;
using std::vector;
using std::string;
typedef long long ll;
typedef pair<int, int> pii;
#define fst first
#define snd second
#define pb(a) push_back(a)
#define mp(a, b) std::make_pair(a, b)
#define debug(...) fprintf(stderr, __VA_ARGS__)
template <typename T> bool chkmax(T& a, T b) { return a < b ? a = b, 1 : 0; }
template <typename T> bool chkmin(T& a, T b) { return a > b ? a = b, 1 : 0; }
const int oo = 0x3f3f3f3f;
string procStatus() {
std::ifstream t("/proc/self/status");
return string(std::istreambuf_iterator<char>(t), std::istreambuf_iterator<char>());
}
template <typename T> T read(T& x) {
int f = 1; x = 0;
char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
return x *= f;
}
const int N = 1000000;
int n, C;
int h[N + 5];
ll s[2][N + 5], dp[N + 5];
ll solve(int x, int y, int mx) {
ll a = y - x - 1;
ll b = -2 * (s[0][y-1] - s[0][x]) - (x != 0) * C - (y != n+1) * C;
ll c = s[1][y-1] - s[1][x] + 1ll * (x != 0) * h[x] * C + 1ll * (y != n+1) * h[y] * C;
ll t;
t = (ll) std::round(-1. * b / 2 / a);
chkmax<ll>(t, mx);
if(x != 0) chkmin(t, (ll) h[x]);
if(y <= n) chkmin(t, (ll) h[y]);
return a * t * t + b * t + c;
}
int main() {
freopen("construct.in", "r", stdin);
freopen("construct.out", "w", stdout);
read(n), read(C);
for(int i = 1; i <= n; ++i) {
read(h[i]);
s[0][i] = s[0][i-1] + h[i];
s[1][i] = s[1][i-1] + 1ll * h[i] * h[i];
}
static int stk[N + 5], top;
h[0] = h[n + 1] = oo;
stk[top ++] = 0;
for(int i = 1; i <= n+1; ++i) {
dp[i] = dp[i-1] + ((i == 1 || i == n+1) ? 0 : 1ll * C * std::abs(h[i] - h[i-1]));
while(top > 0 && h[stk[top-1]] <= h[i]) {
if(top > 1)
chkmin(dp[i], dp[stk[top-2]] + solve(stk[top-2], i, h[stk[top-1]]));
-- top;
}
stk[top ++] = i;
}
printf("%lld\n", dp[n+1]);
return 0;
}
T2
當蔬菜的出現次數比較多的時候可以對每種蔬菜維護二維前綴和並且根據定義計算答案
當蔬菜的出現次數比較少的時候考慮平方的轉化,相當於計算有多少個點被詢問區域包含,實際上等價於四維偏序
綜合分析兩種算法的複雜度並且選取合適的出現次數分界值\(k\),最終複雜度爲\(O(\frac{n^2}{k}(n^2+q)+(n^2k+q)\log^3 n)\),那麼根據這個式子可知\(k=\sqrt{\frac{n^2+q}{\log^3 n}}\)的時候最優
#include <bits/stdc++.h>
using std::pair;
using std::vector;
using std::string;
typedef long long ll;
typedef pair<int, int> pii;
#define fst first
#define snd second
#define pb(a) push_back(a)
#define mp(a, b) std::make_pair(a, b)
#define debug(...) fprintf(stderr, __VA_ARGS__)
template <typename T> bool chkmax(T& a, T b) { return a < b ? a = b, 1 : 0; }
template <typename T> bool chkmin(T& a, T b) { return a > b ? a = b, 1 : 0; }
const int oo = 0x3f3f3f3f;
string procStatus() {
std::ifstream t("/proc/self/status");
return string(std::istreambuf_iterator<char>(t), std::istreambuf_iterator<char>());
}
template <typename T> T read(T& x) {
int f = 1; x = 0;
char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
return x *= f;
}
const int K = 40;
const int N = 200;
const int Q = 100000;
int n, m, q;
struct BIT {
#define lowbit(x) (x & -x)
int c[N + 5][N + 5][N + 5];
void add(int x, int y, int z) {
for(int i = x; i <= m; i += lowbit(i))
for(int j = y; j <= n; j += lowbit(j))
for(int k = z; k <= m; k += lowbit(k)) ++ c[i][j][k];
}
int query(int x, int y, int z) {
int res = 0;
for(int i = x; i > 0; i -= lowbit(i))
for(int j = y; j > 0; j -= lowbit(j))
for(int k = z; k > 0; k -= lowbit(k)) res += c[i][j][k];
return res;
}
} bit;
struct query {
int x1, y1, x2, y2, id;
void input(int _id) {
id = _id;
read(x1), read(y1);
read(x2), read(y2);
}
bool operator < (const query& rhs) const {
return x1 > rhs.x1;
}
};
vector <int> d;
vector <query> mod;
vector <pii> p[Q + 5];
query que[Q + 5];
int ans[Q + 5], cnt[N*N + 5];
int a[N + 5][N + 5], b[N + 5][N + 5];
inline int pw2(int x) { return x * x; }
void calc(int col) {
for(int i = 1; i <= n; ++i) {
for(int j = 1; j <= m; ++j) b[i][j] = b[i][j-1] + (a[i][j] == col);
for(int j = 1; j <= m; ++j) b[i][j] = b[i][j] + b[i-1][j];
}
for(int i = 1; i <= q; ++i) {
int x1 = que[i].x1, y1 = que[i].y1;
int x2 = que[i].x2, y2 = que[i].y2;
ans[que[i].id] += pw2(b[x2][y2] - b[x1-1][y2] - b[x2][y1-1] +
b[x1-1][y1-1]);
}
}
void input() {
read(n), read(m); read(q);
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j) d.pb(read(a[i][j]));
for(int i = 1; i <= q; ++i) que[i].input(i);
}
void solve() {
std::sort(d.begin(), d.end());
d.erase(std::unique(d.begin(), d.end()), d.end());
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j) {
a[i][j] = std::lower_bound(d.begin(), d.end(),
a[i][j]) - d.begin();
++ cnt[a[i][j]];
p[a[i][j]].pb(mp(i, j));
}
for(int i = 0; i < (int) d.size(); ++i) {
if(cnt[i] >= K) {
calc(i);
} else {
for(auto x : p[i])
for(auto y : p[i]) {
query temp;
temp.x1 = x.fst, temp.y1 = x.snd;
temp.x2 = y.fst, temp.y2 = y.snd;
if(temp.x1 > temp.x2) std::swap(temp.x1, temp.x2);
if(temp.y1 > temp.y2) std::swap(temp.y1, temp.y2);
mod.pb(temp);
}
}
}
std::sort(que + 1, que + q + 1);
std::sort(mod.begin(), mod.end());
for(int i = 1, j = 0; i <= q; ++i) {
while(j < (int) mod.size() && mod[j].x1 >= que[i].x1) {
bit.add(m - mod[j].y1 + 1, mod[j].x2, mod[j].y2);
++ j;
}
ans[que[i].id] += bit.query(m - que[i].y1 + 1, que[i].x2, que[i].y2);
}
for(int i = 1; i <= q; ++i) printf("%d\n", ans[i]);
}
int main() {
freopen("vegetable.in", "r", stdin);
freopen("vegetable.out", "w", stdout);
input();
solve();
return 0;
}
T3
顯然危險程度就是樹的直徑,斷開邊之後會得到兩個聯通塊,假設兩個聯通塊的直徑分別爲\(l_1,l_2\),根據直徑的性質,連接兩個聯通塊之後新的直徑長度最小是\(\max\{l_1,l_2,\lceil \frac{l_1}{2}\rceil+\lceil\frac{l_2}{2}\rceil+1\}\)
然後考慮維護,由於合併兩個聯通塊之後新的直徑的兩個端點一定會在原來的直徑端點的並中產生,可以處理每一個子樹的直徑端點,每次考慮\(i\)到\(f_{a_i}\)的邊時只需要分別求出兩個塊的直徑端點即可,在每個點上維護子樹前綴聯通塊和後綴聯通塊的直徑端點即可快速合併
#include <bits/stdc++.h>
using std::pair;
using std::vector;
using std::string;
typedef long long ll;
typedef pair<int, int> pii;
#define fst first
#define snd second
#define pb(a) push_back(a)
#define mp(a, b) std::make_pair(a, b)
#define debug(...) fprintf(stderr, __VA_ARGS__)
template <typename T> bool chkmax(T& a, T b)
{ return a < b ? a = b, 1 : 0; }
template <typename T> bool chkmin(T& a, T b)
{ return a > b ? a = b, 1 : 0; }
const int oo = 0x3f3f3f3f;
string procStatus() {
std::ifstream t("/proc/self/status");
return string(std::istreambuf_iterator<char>(t), std::istreambuf_iterator<char>());
}
template <typename T> T read(T& x) {
int f = 1; x = 0;
char ch = getchar();
for(;!isdigit(ch); ch = getchar()) if(ch == '-') f = -1;
for(; isdigit(ch); ch = getchar()) x = x * 10 + ch - 48;
return x *= f;
}
const int N = 300000;
struct diameter { int x, y, d; };
int sz[N + 5];
int fa[N + 5][21], dep[N + 5];
int st[N + 5], to[(N << 1) + 5], nxt[(N << 1) + 5], e = 1;
inline void addedge(int u, int v) { to[++ e] = v; nxt[e] = st[u]; st[u] = e; }
int get_up(int x, int d) {
for(int i = 0; d > 0; ++i, d >>= 1)
if(d & 1) x = fa[x][i];
return x;
}
int get_lca(int x, int y) {
if(dep[x] < dep[y]) std::swap(x, y);
for(int i = 0, d = dep[x] - dep[y]; d > 0; ++i, d >>= 1)
if(d & 1) x = fa[x][i];
if(x == y) return x;
for(int i = 20; i >= 0; --i)
if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
return fa[x][0];
}
inline int get_dis(int x, int y, int lca = 0) {
if(lca) return dep[x] + dep[y] - 2*dep[lca];
return dep[x] + dep[y] - 2*dep[get_lca(x, y)];
}
inline int get_mid(int x, int y) {
int r = get_lca(x, y), dis = get_dis(x, y) >> 1;
return (dep[x] - dep[r] >= dis) ? get_up(x, dis) : get_up(y, dis);
}
inline void merge(diameter& a, diameter b) {
int len = a.d, x = a.x, y = a.y;
if(chkmax(len, b.d)) a.x = b.x, a.y = b.y;
if(chkmax(len, get_dis(x, b.x))) a.x = x, a.y = b.x;
if(chkmax(len, get_dis(x, b.y))) a.x = x, a.y = b.y;
if(chkmax(len, get_dis(y, b.x))) a.x = y, a.y = b.x;
if(chkmax(len, get_dis(y, b.y))) a.x = y, a.y = b.y;
a.d = len;
}
diameter sub[N + 5];
void dfs(int u, int f = 0) {
fa[u][0] = f;
dep[u] = dep[f] + 1;
sub[u] = (diameter) { u, u, 0 };
for(int i = 1; i < 21; ++i) fa[u][i] = fa[fa[u][i-1]][i-1];
sz[u] = 1;
for(int i = st[u]; i; i = nxt[i]) {
int v = to[i];
if(v == f) continue;
dfs(v, u); sz[u] ++;
merge(sub[u], sub[v]);
}
}
int ans = oo;
int length[N + 5];
int sx, sy, tx0, tx1, ty0, ty1;
pair<diameter, int> *pre[N + 5];
void dfs1(int u, diameter lst, int f = 0) {
if(f > 0) {
int len = std::max(std::max(lst.d,
(lst.d + 1) / 2 + (sub[u].d + 1) / 2 + 1), sub[u].d);
if(chkmin(ans, length[u-1] = len)) {
sx = u, sy = f;
tx0 = lst.x, tx1 = lst.y;
ty0 = sub[u].x, ty1 = sub[u].y;
}
}
int c = 0;
pre[u] = new pair<diameter, int> [sz[u] + 5];
pre[u][c ++] = mp(lst, -1);
for(int i = st[u]; i; i = nxt[i]) {
int v = to[i];
if(v == f) continue;
diameter x = pre[u][c-1].fst;
merge(x, sub[v]); pre[u][c ++] = mp(x, v);
}
diameter suf = lst, temp;
merge(suf, (diameter) { u, u, 0 });
for(int i = c - 1; i > 0; --i) {
int v = pre[u][i].snd;
merge(temp = suf, pre[u][i-1].fst);
dfs1(v, temp, u); merge(suf, sub[v]);
}
}
int n;
int main() {
freopen("league.in", "r", stdin);
freopen("league.out", "w", stdout);
read(n);
for(int i = 1; i < n; ++i) {
static int u, v;
read(u), read(v);
addedge(u, v); addedge(v, u);
}
dfs(1);
dfs1(1, (diameter) { 1, 1, 0 });
vector<int> plan;
for(int i = 1; i < n; ++i) if(length[i] == ans) plan.pb(i);
printf("%d\n", ans);
printf("%lu ", plan.size()); for(auto v : plan) printf("%d ", v);
printf("\n%d %d %d %d\n", sx, sy, get_mid(tx0, tx1), get_mid(ty0, ty1));
return 0;
}
下午講課內容:OI中的數學方法
GCD
\(gcd(x^a-1,x^b-1)=x^{gcd(a,b)}-1\)
證明:
不妨假設\(a\gt b\),那麼一定有\(a=b\times k+t(k,t\in N_+)\)
那麼\(gcd(x^{bk+t}-1,x^b-1)=gcd(x^{bk}\times x^t-1,x^b-1)=gcd((x^b)^k\times x^t-1,x^b-1)\)
\(gcd(fib_a,fib_b)=fit_{gcd(a,b)}\)
歐拉定理及其拓展
若有\(\gcd(a,p)=1\),那麼\(a^\varphi(p)\equiv 1\pmod p\)
一般情況:\(a^t\equiv a^{\min(t,t\mod \varphi(p)+\varphi(p))}\pmod p\)
同餘方程
形如\(x\equiv a_i\pmod {p_i}\)
由此拓展出中國剩餘定理
中國剩餘定理
若\(p_i\)兩兩互質,則存在通解
\[P=\prod_i p_i\]
\[P_i=\frac{P}{p_i}\]
\[T_i=P_i^{-1}\mod p_i\]
\[x\equiv\sum_i a_iT_iP_i\]
其拓展形式:
考慮合併兩個方程:
\[
\begin{aligned}
x &\equiv a_1 \pmod{p_1} \\
x &\equiv a_2 \pmod{p_2} \\
x &= a_1 + k_1p_1 = a_2 + k_2p_2 \\
\end{aligned}
\]
令\(t=\gcd(p_1,p_2)\),則有
\[
\begin{aligned}
k_1\frac{p_1}{t}&\equiv \frac{a_2 - a_1}{t} \pmod{\frac{p_2}{t}}\\
k_1 &\equiv \frac{a_2 - a_1}{t} \times (\frac{p_1}{t})^{-1} \pmod{\frac{p_2}{t}} \\
x &\equiv a_1 + p_1 \left(\frac{a_2 - a_1}{t} \times (\frac{p_1}{t})^{-1} \bmod \frac{p_2}{t} \right) \pmod{\frac{p_1p_2}{t}}
\end{aligned}
\]
積性函數
令\(n = p_1^{e_1}p_2^{e_2} \cdots p_k^{e_k}\):
- \(\epsilon(n) = [n = 1]\)
- \(\mathrm{Id}(n) = n\)
- \(\varphi(n) = n\prod_{i=1}^{k} (1 - \frac{1}{p_i})\)
- \(\mathrm{d}(n) = \sum_{d|n} 1\)
- \(\sigma(n) = \sum_{d|n} d\)
- \(\lambda(n) = (-1)^k\)
Dirichlet卷積
\[(f*g)(n) = \sum_{d|n} f(d) g(\frac{n}{d})\]
\(\mu * 1 = \epsilon\)
\(\mathrm{Id} = \varphi * 1 \Rightarrow \varphi = \mathrm{Id} * \mu\)
\(\mathrm{d} = 1 * 1 \Rightarrow 1 = \mu * \mathrm{d}\)
\(\sigma = \mathrm{Id} * 1 \Rightarrow \mathrm{Id} = \mu * \sigma \Rightarrow \sigma = \varphi * \mathrm{d}\)
\(d(ij) = \sum_{x|i}\sum_{y|j} [\gcd(x, y) = 1]\)
\(\sigma(ij) = \sum_{x|i}\sum_{y|j} [\gcd(x, y) = 1] \frac{iy}{x}\)
狄利克雷卷積滿足交換律, 結合律, 兩個積性函數的卷積也是積性的
組合數的計算
通過一個例題來講解
計算:\[{n \choose m} \bmod p\]
\(n, m \le 5000\)
\(n, m \le 10^6, p\) 是質數
\(n, m \le 10^{18}, p \le 10^6, p\) 是質數
\(n, m \le 10^{18}, p \le 10^6\).
第一個部分分:暴力
第二個部分分:預處理階乘然後算
第三個部分分:Lucas定理
第四個部分分:擴展Lucas定理
基本組合恆等式
\[ \begin{aligned} & \sum_{i=0}^{n} {n \choose i} = 2 ^ n \\ & \sum_{i=0}^{n} {i \choose x} = {n+1 \choose x+1} \\ & \sum_{i=0}^{n} {k+i \choose i} = {k+n+1 \choose n} \\ & \sum_{i=0}^{m} {m \choose i} {n-m \choose m-i} = {n \choose m} \end{aligned} \]
第一類Stirling數
定義: \(\begin{bmatrix} n \\ m \end{bmatrix}\) 表示將\(n\) 個物品分爲 \(m\) 個無序非空環的方案數
遞推式:\[\begin{bmatrix} n \\ m \end{bmatrix} = \begin{bmatrix} n-1 \\ m-1 \end{bmatrix} + (n-1)\begin{bmatrix} n-1 \\ m \end{bmatrix}\]
生成函數:
\[ \begin{aligned} & x^{\overline{n}} = \sum_{i=0}^{n} \begin{bmatrix} n \\ i \end{bmatrix} x^i \\ & x^{\underline{n}}= \sum_{i=0}^{n} (-1)^{n-i} \begin{bmatrix} n \\ i \end{bmatrix} x^i \end{aligned} \]
第二類Stirling數
定義: \(n \brace m\) 表示將 \(n\) 個物品分成 \(m\) 個無序非空集合的方案數
\[{n \brace m} = {n-1 \brace m-1} + m{n-1 \brace m}\]
生成函數:
\[ \begin{aligned} & x^n = \sum_{i=0}^{n} {n \brace i} x^{\underline{i}} \\ & m!{n \brace m} = \sum_{i=0}^{m} (-1)^{m-i} {m \choose i} i^n \end{aligned} \]
多項式的差分序列
定義 \(\Delta^{k} f(n)\) 爲 \(f(n)\) 的 \(k\) 階差分序列, 並且:
\[\Delta^{k} f(n) = \begin{cases} f(n), & \mathrm{k = 0} \\ \Delta^{k-1} f(n+1) - \Delta^{k-1} f(n), & \mathrm{otherwise} \end{cases} \]
容易發現差分序列的具有線性性, 特別地, 多項式:
\[f(x) = \{0, 0, 0, \cdots, 1\} \mid x \in [0, n]\]
差分序列的第一列是 \(\{0, 0, 0, \cdots, 1\}\) , 事實上這個多項式就是
\[\frac{1}{n!} \prod_{i=0}^{n-1} (x - i) = {x \choose n}\]
多項式插值
已知一個 \(n\) 次多項式的 \(n+1\) 個點值, 求這個多項式的係數表示
牛頓插值
求出多項式的 \(n\) 階差分序列第一列 \(\{c_i\}\), 可以將多項式表示成:
\[f(x) = \sum_{i=0}^{n} c_i {x \choose i}\]
拉格朗日插值
考慮構造一個經過所有給定點的多項式:
\[
\begin{aligned}
g_i(x) = \prod_{j=0, j \neq i}^{n} \frac{x - x_j}{x_i - x_j} \\
f(x) = \sum_{i=0}^{n} y_ig_i(x)
\end{aligned}
\]
自然數冪和
求\[f_k(n) = \sum_{i=0}^{n} i^k\]
解:
直接用第二類斯特林數展開:
\[
\begin{aligned}
f_k(n) &= \sum_{i=0}^{n} \sum_{j=0}^{k} {k \brace j} i^{\underline{j}} \\
&= \sum_{j=0}^{k} {k \brace j} j! \sum_{i=0}^{n} {i \choose j} \\
&= \sum_{j=0}^{k} {k \brace j} j! {n + 1 \choose j + 1}
\end{aligned}
\]
或者用拉格朗日插值
通過上一個方法我們知道 \(f_k(n)\) 是一個關於 \(n\) 的 \(k+1\) 次多項式,
於是直接拉格朗日插值即可, 並且由於係數的特殊性質, 可以做到 \(O(k)\)
另外:
Bernoulli數
定義 \(B_i\) 爲伯努利數, 滿足:
\[\sum_{i=0}^{m} B_i {m+1 \choose i} = [m = 0]\]
那麼有:
\[f_k(n-1) = \frac{1}{k+1} \sum_{i=0}^{k} B_i n^{k + 1 - i} {k + 1 \choose i}\]
容斥與反演
\(\mathrm{Min-Max}\) 容斥:\[\max(S) = \sum_{T \subseteq S, T \neq \varnothing} \min(T) ^ {(-1) ^ {|T|-1}}\]
拓展形式:\[\mathrm{lcm}(S) = \prod_{T \subseteq S, T \neq \varnothing} \gcd(T) ^ {(-1) ^ {|T|-1}}\]
二項式反演:
\[ \begin{aligned} f(n) &= \sum_{i=0}^{n} {n \choose i} g(i)\\ \Leftrightarrow g(n) &= \sum_{i=0}^{n} (-1)^{n-i} {n \choose i} f(i) \end{aligned} \]Stirling反演:
\[ \begin{aligned} f(n) &= \sum_{i=0}^{n} {n \brace i} g(i) \\ \Leftrightarrow g(n) &= \sum_{i=0}^{n} (-1)^{n-i} \begin{bmatrix} n \\ i \end{bmatrix} f(i) \end{aligned} \]
例題
BZOJ4833
已知:
\[
\begin{aligned}
f(n) &=
\begin{cases}
0, & \textrm{n = 0} \\
1, & \textrm{n = 1} \\
2f(n-1) + f(n-2), & \textrm{otherwise} \\
\end{cases} \\
g(n) &= \mathrm{lcm}(f(1), f(2), \cdots, f(n))
\end{aligned}
\]
求:\[\sum_{i=1}^{n} g(i) \times i\]
\(n \le 10^6\)
解:
首先有 \(\gcd(f(i), f(j)) = f(\gcd(i, j))\)
根據 \(\mathrm{Min-Max}\) 容斥有:
\[ \begin{aligned} g(n) &= \prod_{T \subseteq S, T \neq \varnothing} \gcd(T)^{(-1)^{|T|+1}} \\ &= \prod_{T \subseteq S, T \neq \varnothing} f(\gcd(T))^{(-1)^{|T|+1}} \end{aligned} \]
\[ \begin{aligned} f(n) &= \prod_{d|n} h(d) \\ \Rightarrow g(n) &= \prod_{d=1}^{n} h(d)^{\sum_{T \subseteq S, T \neq \varnothing} [d \mid gcd(T)] (-1)^{|T|+1}} \\ &= \prod_{d=1}^{n} h(d) \end{aligned} \]
Square
給出一個 \(n \times m\) 大小的矩形,
每個位置可以填上 \([1,c]\) 中的任意一個數,
要求填好後任意兩行互不等價且任意兩列互不等價,
兩行或兩列等價當且僅當對應位置完全相同, 求方案數
\(n, m \le 5000\)
解:
首先我們有一個很簡單的方式使得列之間互不等價, 對於任意一列,總方案數是 \(c^n\), 那麼使得列與列之間互不相同的方案數爲 \((c^n)^{\underline{m}}\)
接下來的問題只與行數有關,
定義 \(g(n)\) 表示 \(n\) 行不保證每行互不等價的方案數,\(f(n)\) 表示 \(n\) 行保證任意兩行互不等價的方案數, 有:
\[
\begin{aligned}
g(n) &= (c^n)^{\underline{m}} \\
&= \sum_{i=0}^{n} {n \brace i} f(i) \\
f(n) &= \sum_{i=0}^{n} (-1)^{n-i} \begin{bmatrix} n \\ i \end{bmatrix} g(i)
\end{aligned}
\]
Sequence
給出一個長度爲 \(n\) 的序列 \(\{a_i\}\) 以及一個數 \(p\), 現在有 \(m\) 次操作,每次操作將 \([l, r]\) 區間內的 \(a_i\) 變成 \(c^{a_i}\), 或者詢問 \([l, r]\) 之間所有 \(a_i\) 的和對 \(p\) 取模的結果
\(n, m \le 5 \times 10^4, p \le 2^{14}\)
解:
對於修改操作可以利用拓展歐拉定理, 維護一個 \(\log(p)\) 層的結構表示每一個 \(a_i\) 的值
由於經過只有最後的 \(\log(p)\) 次操作是有效的,所以任意的兩個相鄰位置在經過 \(\log(p)\) 次操作後會變得等價,在最外層維護一個 std::set
記錄等價的區間, 然後用線段樹做詢問