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;
}