補題記錄

HaneDaniko發表於2024-09-13

Todo List (\(6/38\))

[3] abc 猜想

注意到 \(\lfloor\frac{a^{b}}{c}\rfloor\mod c=\lfloor\frac{a^{b}-kc^{2}}{c}\rfloor\mod c=\lfloor\frac{a^{b}\mod c}{c}\rfloor\mod c\)

快速冪即可

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
#define int long long
int a,b,c;
int power(int a,int t,int p){
	int base=a,ans=1;
	while(t){
		if(t&1){
			ans=ans*base%p;
		}
		base=base*base%p;
		t>>=1;
	}
	return ans;
}
signed main(){
	read(a,b,c);
	cout<<(power(a,b,c*c)/c+c)%c<<endl;
}

[3] 簡單的排列最最佳化題

\(n^{2}\) 的解法是顯然的

考慮如何 \(O(n)\) 做,需要我們從上一個狀態轉移到當前狀態,我們把數和貢獻分別分成 \(p_i\le i\)\(p_i\gt i\) 兩部分,首先簡單手摸一下可以發現每次兩部分答案的增加/減小量恰好就是兩部分的數字之和,而每次兩部分答案顯然會一個增加 \(1\),一個減小 \(1\)(排列的性質)

需要考慮的就是邊界情況,邊界有一個為最左端與最右端的轉換,還有一個 \(p_i=i\) 時的轉換,前者可以直接套式子,對於後者,因為我們只關心數量,因此考慮記錄沒一個時刻有幾個到達該轉換的值即可

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
#define int long long
int a,b,c;
int power(int a,int t,int p){
	int base=a,ans=1;
	while(t){
		if(t&1){
			ans=ans*base%p;
		}
		base=base*base%p;
		t>>=1;
	}
	return ans;
}
int n;
int p[10000001];
int rcnt,lcnt,rtot,ltot;
int dx[10000001];
signed main(){
    read(n);
    for(int i=1;i<=n;++i){
        read(p[i]);
    }
    for(int i=1;i<=n;++i){
        if(p[i]<=i){
            lcnt++;
            ltot+=(i-p[i]);
        }
		else{
            dx[p[i]-i]++;
            rcnt++;
            rtot+=(p[i]-i);
        }
    }
    int ans=ltot+rtot,ansid=0;
    for(int i=1;i<=n-1;++i){
        rtot-=rcnt;
        rcnt-=dx[i];
        ltot+=lcnt;
        lcnt+=dx[i];
        ltot-=n-p[n-i+1]+1;
		lcnt--;
        if(p[n-i+1]>1){
			dx[p[n-i+1]+i-1]++;
			rtot+=p[n-i+1]-1;
			rcnt++;
		}
        else{
			lcnt++;
		}
        if(ltot+rtot<ans){
			ans=ltot+rtot;
			ansid=i;
		}
    }
    cout<<ansid<<" "<<ans<<endl;
}

[1] mine

設計 \(f_{i,0/1/2}\) 表示進行到第 \(i\) 位時,需要下一位是雷/不是雷,或者該位是雷的方案數

當該為是 \(0\) 時,應從上一位的 \(0\) 狀態轉移,並要求下一位為 \(0\)

當該位是 \(1\) 時,可以從上一位的 \(0\) 狀態轉移,並要求下一位為雷,或者從上一位的 \(2\) 狀態轉移,要求下一位為 \(0\)

當該為是 \(2\) 時,應從上一位的 \(2\) 狀態轉移,並要求下一位是雷

當該為是雷時,應從上一位的 \(1\) 狀態轉移

起始狀態需要注意,起始的 \(i\) 需要特殊處理

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define int long long
//#ifdef ONLINE_JUDGE
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
//#else
//#include<hdk/lib.h>
//#endif
string s;
int f[1000001][3];
const int p=1e9+7;
/* 0 mine : 1 mine : is mine*/
signed main(){
	cin>>s;
	if(s[0]=='0' or s[0]=='?') f[0][0]=1;
	if(s[0]=='1' or s[0]=='?') f[0][1]=1;
	if(s[0]=='*' or s[0]=='?') f[0][2]=1;
	for(int i=1;i<=s.length()-1;++i){
		if(s[i]=='0' or s[i]=='?'){
			f[i][0]+=f[i-1][0];
		}
		if(s[i]=='1' or s[i]=='?'){
			f[i][0]+=f[i-1][2];
			f[i][1]+=f[i-1][0];
		}
		if(s[i]=='2' or s[i]=='?'){
			f[i][1]+=f[i-1][2];
		}
		if(s[i]=='*' or s[i]=='?'){
			f[i][2]+=f[i-1][1]+f[i-1][2];
		}
		f[i][0]%=p;
		f[i][1]%=p;
		f[i][2]%=p;
//		cout<<f[i][0]<<" "<<f[i][1]<<" "<<f[i][2]<<endl;
	}
	cout<<(f[s.length()-1][0]+f[s.length()-1][2])%p;
}

[2] 序列

\(1\)\(n\) 列舉 \(r\) ,設 \(f_{i}\) 表示區間 \([i,r]\) 中僅出現一次的數的個數,考慮 \(r\)
\(r+1\) 的變化

  • \(a_{r+1}\) 還未出現過,則 \([1,r+1]\) 內的 \(f\) 都加 \(1\)
  • 否則記 \(a_{r+1}\) 上次出現時的下標為 \(j\),上上次出現時的下標為 \(k\),則 \([j+1,r+1]\) 內的 \(f\) 值都加 \(1\), \([k+1,j]\) 內的 \(f\) 值都減 \(1\)

序列的合法條件即為任意時刻 \(f_{i}\) 的值均大於零, 用線段樹維護加減操作和區間最小值,同時記錄每個值前兩次出現的位置即可

這個做法是很經典的套路,可以用來統計具有某種特徵的區間的數量,列舉區間右端點 ,在所有左端點維護區間的資訊,即可快速統計所有區間.

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
//#ifdef ONLINE_JUDGE
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
//#else
//#include<hdk/lib.h>
//#endif
struct tree{
	int l,r;
	int lazy;
	int minn;
}t[800001];
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
void build(int id,int l,int r){
	t[id].l=l;t[id].r=r;
	t[id].lazy=t[id].minn=0;
	if(l==r){
		return;
	}
	int mid(l,r);
	build(tol,l,mid);
	build(tor,mid+1,r);
}
void pushdown(int id){
	if(t[id].lazy){
		t[tol].lazy+=t[id].lazy;
		t[tor].lazy+=t[id].lazy;
		t[tol].minn+=t[id].lazy;
		t[tor].minn+=t[id].lazy;
		t[id].lazy=0;
	}
}
void change(int id,int l,int r,int k){
//	cout<<"change "<<id<<" "<<l<<" "<<r<<" "<<t[id].l<<" "<<t[id].r<<" "<<k<<endl;
	if(l<=t[id].l and t[id].r<=r){
		t[id].minn+=k;
		t[id].lazy+=k;
		return;
	}
	pushdown(id);
	if(r<=t[tol].r) change(tol,l,r,k);
	else if(l>=t[tor].l) change(tor,l,r,k);
	else{
		int mid(t[id].l,t[id].r);
		change(tol,l,mid,k);
		change(tor,mid+1,r,k);
	}
	t[id].minn=min(t[tol].minn,t[tor].minn);
}
int ask(int id,int l,int r){
	if(l<=t[id].l and t[id].r<=r){
		return t[id].minn;
	}
	pushdown(id);
	if(r<=t[tol].r){
		return ask(tol,l,r);
	}
	else if(l>=t[tor].l){
		return ask(tor,l,r);
	}
	else{
		int mid(t[id].l,t[id].r);
		return min(ask(tol,l,mid),ask(tor,mid+1,r));
	}
}
map<int,int>mp;
int cnt=0;
int T,n;
int a[200001];
int last[200001],l_last[200001];
int main(){
	cin>>T;
	while(T--){
		cnt=0;
		read(n);
		build(1,1,n);
		memset(last,0,sizeof last);
		memset(l_last,0,sizeof l_last);
		mp.clear();
		for(int i=1;i<=n;++i){
			read(a[i]);
			if(!mp.count(a[i])) mp[a[i]]=++cnt;
			a[i]=mp[a[i]];
		}
		bool flag=false;
		for(int i=1;i<=n;++i){
			if(!last[a[i]]){
//				cout<<"add [1,"<<i+1<<"]"<<1<<endl;
				last[a[i]]=i;
				change(1,1,i,1);
			}
			else{
				change(1,last[a[i]]+1,i,1);
//				cout<<"add ["<<last[a[i]]+1<<","<<i+1<<"]"<<1<<endl;
				change(1,l_last[a[i]]+1,last[a[i]],-1);
//				cout<<"add ["<<l_last[a[i]]+1<<","<<last[a[i]]<<"]"<<-1<<endl;
				l_last[a[i]]=last[a[i]];
				last[a[i]]=i;
			}
			if(ask(1,1,i)<=0){
//				cout<<i<<" "<<ask(1,1,i)<<" ";
				cout<<"boring"<<endl;
				flag=true;
				break;
			}
		}
		if(!flag){
			cout<<"non-boring"<<endl;
		}
	}
}

[2] Leagcy

線段樹最佳化建圖板子題

考慮到,如果我們需要從節點 \(x\)\([l,r]\) 中的所有節點連邊,我們可以考慮建一顆線段樹,分別將 \(x\) 與符合要求的區間節點連邊,再將區間節點與其子節點連邊權為 \(0\) 的邊即可

本題既有單點連線區間,也有區間連線單點,對兩種情況分別建一顆線段樹即可,區間連線單點則需要建兒子指向父親的線段樹

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
//#ifdef ONLINE_JUDGE
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
//#else
//#include<hdk/lib.h>
//#endif
#define int long long
struct tree{
	int l,r;
}t[400001];
int n,m,st;
int dis[1000001];
int leaf[100001];
bool vis[1000001];
#define mid(l,r) mid=((l)+(r))/2
#define tol (id*2)
#define tor (id*2+1)
const int dx=5e5;
struct edge{
	int to,w;
};
vector<edge>e[1000001];
void build(int id,int l,int r){
	t[id].l=l;t[id].r=r;
	if(l==r){
		leaf[l]=id;
		return;
	}
	int mid(l,r);
	e[id].push_back({tol,0});
	e[id].push_back({tor,0});
	e[tol+dx].push_back({id+dx,0});
	e[tor+dx].push_back({id+dx,0});
	build(tol,l,mid);
	build(tor,mid+1,r);
}
void connect(int id,int l,int r,int to,int w,int tp){
	if(l<=t[id].l and t[id].r<=r){
		if(tp){
			e[id+dx].push_back({to,w});
		}
		else{
			e[to].push_back({id,w});
		}
		return;
	}
	int mid(t[id].l,t[id].r);
	if(r<=mid) connect(tol,l,r,to,w,tp);
	else if(l>mid) connect(tor,l,r,to,w,tp);
	else{
		connect(tol,l,mid,to,w,tp);
		connect(tor,mid+1,r,to,w,tp);
	}
}
struct node{
	int id,val;
	bool operator <(const node &A)const{
		return val>A.val;
	}
};
void dij(int s){
	priority_queue<node>q;
	memset(dis,0x3f,sizeof dis);
	dis[leaf[s]+dx]=0;
	q.push({leaf[s]+dx,dis[leaf[s]+dx]});
	while(!q.empty()){
		node u=q.top();
		q.pop();
		if(vis[u.id]) continue;
		for(edge i:e[u.id]){
			if(dis[i.to]>dis[u.id]+i.w){
				dis[i.to]=dis[u.id]+i.w;
				q.push({i.to,dis[i.to]});
			}
		} 
	}
}
signed main(){
	read(n,m,st);
	build(1,1,n);
	for(int i=1;i<=m;++i){
		int op,from,to,l,r,val;
		read(op);
		if(op==1){
			read(from,to,val);
			e[leaf[from]].push_back({leaf[to],val});
		}
		else{
			read(to,l,r,val);
			connect(1,l,r,leaf[to],val,op&1);
		}
	}
	for(int i=1;i<=n;++i){
		e[leaf[i]].push_back({leaf[i]+dx,0});
		e[leaf[i]+dx].push_back({leaf[i],0});	
	}
	dij(st);
	for(int i=1;i<=n;++i){
		if(dis[leaf[i]]==0x3f3f3f3f3f3f3f3f) cout<<"-1 ";
		else cout<<dis[leaf[i]]<<" ";
	}
}

[2] DP 搬運工 2

考慮從 \(1\)\(n\) 插入所有數到序列中

這樣做的話就會有一個很好的性質,就是不管這個數插到哪裡,它總是最大的數,所以總會使合法的狀態增加 \(1\)(除非插在兩邊)

反例就是當插入的這個數破壞了原來合法的一組的時候,同時會讓答案減一,這樣就相當於不變了

\(f_{i,j}\) 表示考慮前 \(i\) 位,合法 \(j\) 組的方案數,考慮從 \(i-1\) 轉移,當我們插入 \(i\) 的時候,一共有 \(2j\) 個位置(在 \(j\) 個本來合法的位置兩邊插入)能夠增加答案的同時破壞一個答案,一共有 \(2\) 個位置(在整個序列首尾插入)能不增加答案,其餘都是增加 \(1\) 的方案

直接轉移即可

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
//#ifdef ONLINE_JUDGE
#define endl '\n'
template<typename T>
void read(T& x){
	x=0;bool sym=0;char c=getchar();
    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
    while(isdigit(c)){x=x*10+c-48;c=getchar();}
    if(sym)x=-x;
}
template<typename T,typename... Args>
void read(T& x,Args&... args){
    read(x);read(args...);
}
//#else
//#include<hdk/lib.h>
//#endif
int n,k;
int f[2001][2001];
const int p=998244353;
int main(){
	read(n,k);
	f[1][0]=1;f[2][0]=2;
	for(int i=3;i<=n;++i){
		for(int j=0;j<=min(i/2,k);++j){
			f[i][j]=(f[i][j]+f[i-1][j]*1ll*(2*j+2))%p;
			f[i][j+1]=(f[i][j+1]+f[i-1][j]*1ll*(i-2*j-2))%p;
		}
	}
	cout<<(f[n][k]+p)%p<<endl;
}

相關文章