寒假集训
Day 1
前言:为什么今天右眼皮总跳……拜托一定要发生点好事啊
今天的调试:
- 方差:首先,
没 。其次,没看到“实数”。最后,推的式子是对的,但统计答案时出错了。怒调半小时(?) - 灯泡:哦一遍过
- 矩阵取数:一是没发现数据极大会爆
,二是 “多测”没“清空” - 子树和:
初值没赋极小值。
线段树
1.1 可能是我人生中的第 道紫题?
本来没打算写的,直到看到同机房的人都写了……
但感觉可以二维数点
然后大概就做完了
码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=6e5+5;
int n,q;
vector <int> tr[N];
int ans[N];
struct node { int id,val,lim; };
vector <node> p[N],ist[N];
int ea[N];
int siz[N],dfn[N],tme,dep[N];
void dfs(int x,int _fa)
{
siz[x]=1,dfn[x]=++tme,dep[x]=dep[_fa]+1;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs(v,x);
siz[x]+=siz[v];
}
ist[dfn[x]].push_back({dep[x],siz[x]-1,0});
}
int lowbit(int x) { return x&-x; }
void add(int x,int y)
{
while (x<N)
{
ea[x]+=y;
x+=lowbit(x);
}
}
int sum(int x)
{
int res=0;
while (x)
{
res+=ea[x];
x-=lowbit(x);
}
return res;
}
void solve()
{
for (int i=1;i<=n;i++)
{
int _size1=ist[i].size();
for (int j=0;j<_size1;j++) add(ist[i][j].id,ist[i][j].val);
int _size2=p[i].size();
for (int j=0;j<_size2;j++) ans[p[i][j].id]+=p[i][j].val*sum(p[i][j].lim);
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q;
for (int i=1,u,v;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v),tr[v].push_back(u);
}
dfs(1,0);
for (int i=1,u,k;i<=q;i++)
{
cin>>u>>k;
p[dfn[u]+siz[u]-1].push_back({i,1,dep[u]+k});
p[dfn[u]+siz[u]-1].push_back({i,-1,dep[u]});
p[dfn[u]].push_back({i,-1,dep[u]+k});
p[dfn[u]].push_back({i,1,dep[u]});
ans[i]+=(siz[u]-1)*min(k,dep[u]-1);
}
solve();
for (int i=1;i<=q;i++) cout<<ans[i]<<"\n";
return 0;
}
线段树合并板子
顾名思义,就是有一颗新的线段树,这棵树上的每个节点是原来两棵线段树的相应结点合并后的值。需要用到动态开点
对于这道板子,不理解为什么要线段树合并 于是乎研究了一下sxht大巨的代码
不喜欢这道题,因为分屏有问题
下面将“不同赈灾粮种类”称为“不同颜色”
这道题显然要树上差分。这大概算是一个……颜色的单修区查,所以要在每个节点建立一棵线段树,维护节点
而这若干棵线段树维护的都是差分信息,所以最后还需要把这些线段树以“前缀和”的方式加在一起——也就是线段树合并
然后就完了(喜
码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,m;
vector <int> tr[N];
struct node{
int ls,rs;
int sum,col;
}smt[N*50];
int rt[N],cnt;
int ans[N];
void push_up(int u)
{
int ls=smt[u].ls,rs=smt[u].rs;
if (smt[ls].sum>=smt[rs].sum)
{
smt[u].sum=smt[ls].sum;
smt[u].col=smt[ls].col;
}
else
{
smt[u].sum=smt[rs].sum;
smt[u].col=smt[rs].col;
}
}
void update(int &u,int l,int r,int pos,int x)
{
if (!u) u=++cnt;
if (l==r)
{
smt[u].sum+=x,smt[u].col=pos;
return ;
}
int mid=(l+r)>>1;
if (mid>=pos) update(smt[u].ls,l,mid,pos,x);
else update(smt[u].rs,mid+1,r,pos,x);
push_up(u);
}
int merge(int u,int v,int l,int r)
{
if (!u||!v) return u+v;
if (l==r) { smt[u].sum+=smt[v].sum; return u; }
int mid=(l+r)>>1;
smt[u].ls=merge(smt[u].ls,smt[v].ls,l,mid);
smt[u].rs=merge(smt[u].rs,smt[v].rs,mid+1,r);
push_up(u);
return u;
}
int dep[N],st[N][25];
void dfs(int x,int _fa)
{
dep[x]=dep[_fa]+1;
st[x][0]=_fa;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs(v,x);
}
}
void init()
{
dfs(1,0);
for (int j=1;j<=20;j++)
for (int i=1;i<=n;i++) st[i][j]=st[st[i][j-1]][j-1];
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
int delta=dep[x]-dep[y],lg=0;
while (delta)
{
if (delta&1) x=st[x][lg];
delta>>=1,lg++;
}
if (x==y) return x;
for (int i=20;i>=0;i--)
{
if (st[x][i]==st[y][i]) continue;
x=st[x][i],y=st[y][i];
}
return st[x][0];
}
void calc(int x,int _fa)
{
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
calc(v,x);
rt[x]=merge(rt[x],rt[v],1,N);
}
if (smt[rt[x]].sum) ans[x]=smt[rt[x]].col;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for (int i=1,u,v;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v),tr[v].push_back(u);
}
init();
int x,y,z;
while (m--)
{
cin>>x>>y>>z;
int ll=lca(x,y);
update(rt[x],1,N,z,1);//树上差分
update(rt[y],1,N,z,1);
update(rt[ll],1,N,z,-1);
update(rt[st[ll][0]],1,N,z,-1);
}
calc(1,0);//“前缀和”
for (int i=1;i<=n;i++) cout<<ans[i]<<"\n";
return 0;
}
DP
直接上强度吧
不上了
2.1 灯泡
看到标题,我们知道这是区间
设状态
那么显然有状态转移方程(沿同一方向走或折返换向)
DP的魅力——超短代码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=52;
int n,c;
int pos[N],sum[N];
int f[N][N][2];
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
memset(f,0x3f,sizeof f);
cin>>n>>c;
for (int i=1;i<=n;i++) { cin>>pos[i]>>sum[i]; sum[i]+=sum[i-1]; }
f[c][c][0]=f[c][c][1]=0;
for (int len=2;len<=n;len++)
for (int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
f[i][j][0]=min(f[i+1][j][0]+(pos[i+1]-pos[i])*(sum[n]-(sum[j]-sum[i])),f[i+1][j][1]+(pos[j]-pos[i])*(sum[n]+sum[i]-sum[j]));
f[i][j][1]=min(f[i][j-1][1]+(pos[j]-pos[j-1])*(sum[n]+sum[i-1]-sum[j-1]),f[i][j-1][0]+(pos[j]-pos[i])*(sum[n]+sum[i-1]-sum[j-1]));
}
cout<<min(f[1][n][0],f[1][n][1]);
return 0;
}
2.2 矩阵取数游戏
哎我真服了一个绿题的高精需求直接卡我一晚上
发现每一行都可以独立计算,把每一行的最大价值相加就是最后的答案
对于每一行,因为每次选数只能从两头选,所有没被选的数一定会形成一个连续的区间。于是乎,设状态
显然有状态转移方程
然后就做完了
然后就又WA又TLE了
打开题解,发现要高精 直接放弃
但是能用int128解决(这我也不会啊)
关于
定义是
然后,这个东西只能四则运算,只能快读快写。。。
void input(__int128 &s)
{
char c=' ';
while (c>'9'||c<'0') c=getchar();
while (c>='0'&&c<='9')
{
s=s*10+(c-'0');
c=getchar();
}
}
void output(__int128 x)
{
if (x==0) return ;
if (x) output(x/10);
putchar(x%10+'0');
}
(好麻烦)
然后WA。为什么呢,因为“多测”忘“清空”了。
史诗级代码
#include <bits/stdc++.h>
using namespace std;
const int N=81;
int n,m,a[N];
__int128 p[92]={1};
__int128 f[N][N];
__int128 ans,sum;
inline void input(__int128 &s)
{
char c=' ';
while (c>'9'||c<'0') c=getchar();
while (c>='0'&&c<='9')
{
s=s*10+(c-'0');
c=getchar();
}
}
inline void output(__int128 x)
{
if (x==0) return ;
if (x) output(x/10);
putchar(x%10+'0');
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for (int i=1;i<=90;i++) p[i]=p[i-1]<<1;
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++) cin>>a[j];
memset(f,0,sizeof f);
f[1][m-1]=2*a[m];
f[2][m]=2*a[1];
for (int len=m-2;len>=0;len--)
for (int i=1;i+len-1<=m;i++)
{
int j=i+len-1;
f[i][j]=max(f[i][j+1]+a[j+1]*p[m-len],f[i-1][j]+a[i-1]*p[m-len]);
}
ans=0;
for (int i=1;i<=m;i++) ans=max(ans,f[i][i-1]);
sum+=ans;
}
if (sum==0) puts("0");
else output(sum);
return 0;
}
2.3 最大子树和
没啥好说的。设状态
但是这题有负数负数负数负数负数
看到最大值
被黄题爆切了……
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=16005;
int n,v[N];
vector <int> tr[N];
int f[N],ans=0xc1c1c1c1c1c1c1c1;
void dfs(int x,int _fa)
{
f[x]=v[x];
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs(v,x);
if (f[v]>0) f[x]+=f[v];
}
ans=max(ans,f[x]);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for (int i=1;i<=n;i++) cin>>v[i];
for (int i=1,a,b;i<n;i++)
{
cin>>a>>b;
tr[a].push_back(b),tr[b].push_back(a);
}
dfs(1,0),cout<<ans;
return 0;
}
Day 2
RMQ啥的
1.1 被绿题爆切了
第一眼,感觉要处理出所有的
第二眼,感觉要找到
因为忘记
全剧终
咳咳,完成上两步后需要一些其他的操作。设状态
这样了话,对于区间
啊啊啊好难讲,还是得感性理解……但感性理解太不严谨了
反正,然后就完了
码
#incIude <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,q,x;
int f[N];
map <int,int> mp;
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q>>x;
for (int i=1,a;i<=n;i++)
{
cin>>a;
mp[a^x]=i;
f[i]=max(f[i-1],mp[a]);
}
int l,r;
while (q--)
{
cin>>l>>r;
if (l<=f[r]) cout<<"yes"<<"\n";
else cout<<"no"<<"\n";
}
return 0;
}
1.2 不是怎么又被绿题爆切了
显然的树上差分+LCA。但是……
归来仍不会树上差分
咳咳,解决树上差分这道题就happy ending了!
首先,这不是我刚开始想的那样头加尾减,因为它会有若干个“尾”,无法处理。所以应是尾加头减,dfs时回溯累加!
最后回溯到lca(u,v)时,它会多加两次,所以它要减减;它的父节点不需要多加,所以它的父节点也需要减减
也就是说,对于形如 u->lca(u,v)->v 的一条路径,它的差分应该是
然后就完了
码
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+5;
int n,k;
vector <int> tr[N];
int dep[N],st[N][30];
int dif[N],ans;
void dfs(int x,int _fa)
{
st[x][0]=_fa,dep[x]=dep[_fa]+1;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs(v,x);
}
}
void init()
{
dfs(1,0);
for (int j=1;j<=20;j++)
for (int i=1;i<=n;i++) st[i][j]=st[st[i][j-1]][j-1];
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
int lg2=0,delta=dep[x]-dep[y];
while (delta)
{
if (delta&1) x=st[x][lg2];
delta>>=1,lg2++;
}
if (x==y) return x;
for (int i=20;i>=0;i--)
{
if (st[x][i]==st[y][i]) continue;
x=st[x][i],y=st[y][i];
}
return st[x][0];
}
void dfs2(int x,int _fa)
{
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs2(v,x);
dif[x]+=dif[v];
}
ans=max(ans,dif[x]);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k;
for (int i=1,u,v;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v),tr[v].push_back(u);
}
init();
for (int i=1,s,t;i<=k;i++)
{
cin>>s>>t;
int ll=lca(s,t);
dif[s]++,dif[t]++,dif[ll]--,dif[st[ll][0]]--;
}
dfs2(1,0),cout<<ans;
return 0;
}
各种DP
2.1 随机抽取一道换根DP
首先,大概手推了下,发现这就是换根
跑完一遍,发现只有根节点统计到位,果断开始换根
设状态
手模后,会发现转移式子:
然后就做完了
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,k;
vector <int> tr[N];
int c[N];
int f1[N][25],f2[N][25];
int fa[N];
void dfs1(int x,int _fa)
{
for (int i=0;i<=k;i++) f1[x][i]=c[x];
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs1(v,x);
for (int j=1;j<=k;j++) f1[x][j]+=f1[v][j-1];
}
for (int i=1;i<=k;i++) f2[x][i]=f1[x][i];
}
void dfs2(int x,int _fa)
{
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
for (int j=2;j<=k;j++) f2[v][j]+=f2[x][j-1]-f1[v][j-2];
f2[v][1]+=f1[x][0];
dfs2(v,x);
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k;
for (int i=1,u,v;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v);
tr[v].push_back(u);
}
for (int i=1;i<=n;i++) cin>>c[i];
dfs1(1,0);
dfs2(1,0);
for (int i=1;i<=n;i++) cout<<f2[i][k]<<"\n";
return 0;
}
2.2 随机抽取一道数位DP
显然的数位DP
因为方案数与前一个选的数密切相关,所以设状态
直接记搜就完了。但是前导0真的很烦很烦哎
所以还要单独记个东西来存是否有前导0
然后莫名其妙调了我一个小时。。。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=12;
int a,b;
int la[N],len;
int f[N][N];
int dfs(int pos,int num,int lim,int flag)
{
if (!pos) return 1;
if (!lim&&f[pos][num]!=-1) return f[pos][num];
int res=0,up=9;
if (lim) up=la[pos];
for (int i=0;i<=up;i++)
{
if (abs(i-num)<2) continue;
if (flag&&!i) res+=dfs(pos-1,-2,lim&&i==up,2);
else res+=dfs(pos-1,i,lim&&i==up,0);
}
if (!lim&&!flag) f[pos][num]=res;
return res;
}
int solve(int x)
{
len=0;
while (x)
{
la[++len]=x%10;
x/=10;
}
return dfs(len,-2,1,1);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
memset(f,-1,sizeof f);
cin>>a>>b;
cout<<solve(b)-solve(a-1);
return 0;
}
2.3 随机抽取一道状压DP
看到环,把链复制成两份就行了(甚至这题都不用完全搞两份?)
Day 3
FWT
听不懂听不懂听不懂听不懂听不懂
FWT 是用于解决对下标进行位运算卷积问题的方法。
(注意上述式子的
一、或卷积
构造
设
(因为最高位为
已知
然后经过上述两次变换,就可以求出
二、与卷积
与或卷积同理,有
然后有
然后有
三、异或卷积
我们需要构造出一个
1.1 板子
不会不会
树链剖分
……一年前其实学过,但学和会不是一个事情
因为树链剖分
一棵树可以被分成不超过
2.1 板子
区修区查,这道题显然要用线段树维护区间和(显然,这里的区间指的是
若是在路径上维护,就要用树剖性质将该路径分成若干条链(每条链中的
那么,先跑两遍
板
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,q,rt,mod;
int c[N],val[N];
vector <int> tr[N];
struct segment_tree
{
struct node {
int l,r;
int sum,tag;
}smt[N<<2];
void push_up(int id) { smt[id].sum=smt[id<<1].sum+smt[(id<<1)|1].sum; }
void push_down(int id)
{
if (!smt[id].tag) return ;
int ls=id<<1,rs=(id<<1)|1;
smt[ls].sum=(smt[ls].sum+(smt[ls].r-smt[ls].l+1)*smt[id].tag%mod)%mod;
smt[rs].sum=(smt[rs].sum+(smt[rs].r-smt[rs].l+1)*smt[id].tag%mod)%mod;
smt[ls].tag+=smt[id].tag,smt[rs].tag+=smt[id].tag,smt[id].tag=0;
}
void build(int id,int l,int r)
{
smt[id].l=l,smt[id].r=r;
if (l==r) { smt[id].sum=val[l]%mod; return ; }
int mid=(l+r)>>1;
build(id<<1,l,mid);
build((id<<1)|1,mid+1,r);
push_up(id);
}
void update(int id,int l,int r,int x)
{
if (smt[id].l>=l&&smt[id].r<=r)
{
smt[id].sum=(smt[id].sum+x*(smt[id].r-smt[id].l+1)+mod)%mod;
smt[id].tag+=x;
return ;
}
push_down(id);
int mid=(smt[id].l+smt[id].r)>>1;
if (mid>=l) update(id<<1,l,r,x);
if (mid+1<=r) update((id<<1)|1,l,r,x);
push_up(id);
}
int query(int id,int l,int r)
{
if (smt[id].l>=l&&smt[id].r<=r) return smt[id].sum;
push_down(id);
int mid=(smt[id].l+smt[id].r)>>1,res=0;
if (mid>=l) res=(res+query(id<<1,l,r)+mod)%mod;
if (mid+1<=r) res=(res+query((id<<1)|1,l,r)+mod)%mod;
return res;
}
}Tr;
int dep[N],siz[N],son[N],fa[N];
int dfn[N],tme,top[N];
void dfs1(int x,int _fa)
{
dep[x]=dep[_fa]+1,fa[x]=_fa,siz[x]=1;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs1(v,x);
if (siz[v]>siz[son[x]]) son[x]=v;
siz[x]+=siz[v];
}
}
void dfs2(int x,int _top)
{
dfn[x]=++tme,top[x]=_top,val[dfn[x]]=c[x];
if (!son[x]) return ;
dfs2(son[x],_top);
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==fa[x]||v==son[x]) continue;
dfs2(v,v);
}
}
void update1(int x,int y,int z)
{
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
Tr.update(1,dfn[top[x]],dfn[x],z%mod);
x=fa[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
Tr.update(1,dfn[y],dfn[x],z%mod);
}
int query1(int x,int y)
{
int res=0;
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
res=(res+Tr.query(1,dfn[top[x]],dfn[x]))%mod;
x=fa[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
res=(res+Tr.query(1,dfn[y],dfn[x]))%mod;
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q>>rt>>mod;
for (int i=1;i<=n;i++) cin>>c[i];
for (int i=1,u,v;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v),tr[v].push_back(u);
}
dfs1(rt,0),dfs2(rt,rt);
Tr.build(1,1,n);
int op,x,y,z;
while (q--)
{
cin>>op;
if (op==1) { cin>>x>>y>>z; update1(x,y,z); }
else if (op==2) { cin>>x>>y; cout<<query1(x,y)<<"\n"; }
else if (op==3) { cin>>x>>z; Tr.update(1,dfn[x],dfn[x]+siz[x]-1,z); }
else { cin>>x; cout<<Tr.query(1,dfn[x],dfn[x]+siz[x]-1)<<"\n"; }
}
return 0;
}
随机抽取一道树链剖分
这不就板子吗?调一些奇奇怪怪的错误就过了
fjj休想碰我羽绒服帽子上的毛
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,q;
vector <int> tr[N];
struct Segment_tree
{
struct node{
int l,r;
int sum,tag;//-1:变0 1:变1
}tr[N<<2];
void push_up(int id) { tr[id].sum=tr[id<<1].sum+tr[id<<1|1].sum; }
void push_down(int id)
{
if (!tr[id].tag) return ;
int ls=id<<1,rs=id<<1|1;
if (tr[id].tag==1)
{
tr[ls].sum=tr[ls].r-tr[ls].l+1;
tr[rs].sum=tr[rs].r-tr[rs].l+1;
tr[ls].tag=tr[rs].tag=1;
}
else
{
tr[ls].sum=tr[rs].sum=0;
tr[ls].tag=tr[rs].tag=-1;
}
tr[id].tag=0;
}
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if (l==r) return ;
int mid=(l+r)>>1;
build(id<<1,l,mid),build(id<<1|1,mid+1,r);
}
void update(int id,int l,int r,int w)
{
if (tr[id].l>=l&&tr[id].r<=r) { tr[id].sum=(tr[id].r-tr[id].l+1)*w; tr[id].tag=(w?1:-1); return ; }
push_down(id);
int mid=(tr[id].l+tr[id].r)>>1;
if (mid>=l) update(id<<1,l,r,w);
if (mid+1<=r) update(id<<1|1,l,r,w);
push_up(id);
}
int query(int id,int l,int r)
{
if (tr[id].l>=l&&tr[id].r<=r) return tr[id].sum;
push_down(id);
int mid=(tr[id].l+tr[id].r)>>1,res=0;
if (mid>=l) res+=query(id<<1,l,r);
if (mid+1<=r) res+=query(id<<1|1,l,r);
return res;
}
}Tr;
int dep[N],siz[N],dfn[N],tme;
int fa[N],son[N],top[N];
void dfs1(int x,int _dep)
{
dep[x]=_dep,siz[x]=1;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
fa[v]=x;
dfs1(v,_dep+1);
siz[x]+=siz[v];
if (siz[v]>siz[son[x]]) son[x]=v;
}
}
void dfs2(int x,int _top)
{
dfn[x]=++tme,top[x]=_top;
if (!son[x]) return ;
dfs2(son[x],_top);
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==son[x]) continue;
dfs2(v,v);
}
}
void _update(int x)
{
while (x)
{
Tr.update(1,dfn[top[x]],dfn[x],1);
x=fa[top[x]];
}
}
int _query(int x)
{
int res=0;
while (x)
{
res+=Tr.query(1,dfn[top[x]],dfn[x]);
x=fa[top[x]];
}
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for (int i=2,a;i<=n;i++) { cin>>a; tr[++a].push_back(i); }
dfs1(1,1);
dfs2(1,1);
Tr.build(1,1,n);
string op;
int x;
cin>>q;
while (q--)
{
cin>>op>>x;
x++;
if (op=="install")
{
cout<<dep[x]-_query(x)<<"\n";
_update(x);
}
else
{
cout<<Tr.query(1,dfn[x],dfn[x]+siz[x]-1)<<"\n";
Tr.update(1,dfn[x],dfn[x]+siz[x]-1,0);
}
}
return 0;
}
再随机抽取一道树链剖分+线段树
这树链剖分不比打CF有意思?
树上路径维护信息,想到树链剖分。每种颜色(宗教)分别统计外加区修,想到线段树
于是乎,开
“调试”点:开若干棵线段树就动态开点,然后用数组
然后直接做做完了
码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,q;
int w[N],c[N];
int rt[N];
vector <int> tr[N];
struct Segment_Tree
{
int cnt=0;
struct node {
int ls,rs;
int mx,sum;
}tr[N<<8];
void push_up(int id) { tr[id].mx=max(tr[tr[id].ls].mx,tr[tr[id].rs].mx); tr[id].sum=tr[tr[id].ls].sum+tr[tr[id].rs].sum; }
void update(int &id,int l,int r,int x,int k)
{
if (!id) id=++cnt;
if (l==r) { tr[id].mx=tr[id].sum=k; return ; }
int mid=(l+r)>>1;
if (mid>=x) update(tr[id].ls,l,mid,x,k);
else update(tr[id].rs,mid+1,r,x,k);
push_up(id);
}
int query_mx(int &id,int l,int r,int ql,int qr)
{
if (l>=ql&&r<=qr) return tr[id].mx;
int mid=(l+r)>>1,res=0;
if (mid>=ql) res=max(res,query_mx(tr[id].ls,l,mid,ql,qr));
if (mid+1<=qr) res=max(res,query_mx(tr[id].rs,mid+1,r,ql,qr));
return res;
}
int query_sum(int &id,int l,int r,int ql,int qr)
{
if (l>=ql&&r<=qr) return tr[id].sum;
int mid=(l+r)>>1,res=0;
if (mid>=ql) res+=query_sum(tr[id].ls,l,mid,ql,qr);
if (mid+1<=qr) res+=query_sum(tr[id].rs,mid+1,r,ql,qr);
return res;
}
}Tr;
int fa[N],son[N],siz[N],dep[N];
int dfn[N],tme,top[N];
void dfs1(int x,int _fa)
{
fa[x]=_fa,siz[x]=1,dep[x]=dep[_fa]+1;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs1(v,x);
siz[x]+=siz[v];
if (siz[v]>siz[son[x]]) son[x]=v;
}
}
void dfs2(int x,int _top)
{
dfn[x]=++tme,top[x]=_top;
if (!son[x]) return ;
dfs2(son[x],_top);
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==fa[x]||v==son[x]) continue;
dfs2(v,v);
}
}
int queryS(int x,int y)
{
int root=rt[c[x]],res=0;
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
res+=Tr.query_sum(root,1,n,dfn[top[x]],dfn[x]);
x=fa[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
res+=Tr.query_sum(root,1,n,dfn[y],dfn[x]);
return res;
}
int queryM(int x,int y)
{
int root=rt[c[x]],res=0;
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
res=max(res,Tr.query_mx(root,1,n,dfn[top[x]],dfn[x]));
x=fa[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
res=max(res,Tr.query_mx(root,1,n,dfn[y],dfn[x]));
return res;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>q;
for (int i=1;i<=n;i++) cin>>w[i]>>c[i];
for (int i=1,u,v;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v),tr[v].push_back(u);
}
dfs1(1,0),dfs2(1,1);
for (int i=1;i<=n;i++) Tr.update(rt[c[i]],1,n,dfn[i],w[i]);
string op;
int x,y;
while (q--)
{
cin>>op>>x>>y;
if (op=="CC") { Tr.update(rt[c[x]],1,n,dfn[x],0); Tr.update(rt[y],1,n,dfn[x],w[x]); c[x]=y; }
else if (op=="CW") { Tr.update(rt[c[x]],1,n,dfn[x],y); w[x]=y; }
else if (op=="QS") cout<<queryS(x,y)<<"\n";
else cout<<queryM(x,y)<<"\n";
}
return 0;
}
最后的树链剖分
轻边重边,不好维护呢。点,好维护nie
所以不如把边的特征在点上表示。为每个点赋值,若一条边两个点的值相同,则是重边,反之则是轻边。这样的话,用线段树记录一下区间左右端点的值,就很好维护了!修改就是附上完全不同的值,加重边就是区间设相同的值
但是在查询的时候要注意小细节(这种细节还是自己推推好啊www)
以及,多测清空……全都清空……不然会出奇奇怪怪的问题……线段树也给我清空啊
调好久!
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int T;
int n,m;
vector <int> tr[N];
struct node{
int l,r;
int lc,rc;
int sum,tag;
node operator +(const node &y) const{
node res={0,0,lc,y.rc,sum+y.sum+(rc==y.lc),0};
return res;
}
void clr() { l=r=lc=rc=sum=tag=0; }
};
struct Segment_Tree
{
node tr[N<<2];
void push_up(int id)
{
tr[id].lc=tr[id<<1].lc,tr[id].rc=tr[id<<1|1].rc;
tr[id].sum=tr[id<<1].sum+tr[id<<1|1].sum+(tr[id<<1].rc==tr[id<<1|1].lc);
}
void push_down(int id)
{
if (!tr[id].tag) return ;
int ls=id<<1,rs=id<<1|1;
tr[ls]={tr[ls].l,tr[ls].r,tr[id].tag,tr[id].tag,tr[ls].r-tr[ls].l,tr[id].tag};
tr[rs]={tr[rs].l,tr[rs].r,tr[id].tag,tr[id].tag,tr[rs].r-tr[rs].l,tr[id].tag};
tr[id].tag=0;
}
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r;
if (l==r)
{
tr[id].lc=tr[id].rc=l;
tr[id].sum=tr[id].tag=0;
return ;
}
int mid=(l+r)>>1;
build(id<<1,l,mid),build(id<<1|1,mid+1,r);
push_up(id);
}
void update(int id,int l,int r,int w)
{
if (tr[id].l>=l&&tr[id].r<=r)
{
tr[id].lc=tr[id].rc=tr[id].tag=w;
tr[id].sum=tr[id].r-tr[id].l;
return ;
}
push_down(id);
int mid=(tr[id].l+tr[id].r)>>1;
if (mid>=l) update(id<<1,l,r,w);
if (mid+1<=r) update(id<<1|1,l,r,w);
push_up(id);
}
node query(int id,int l,int r)
{
if (tr[id].l>=l&&tr[id].r<=r) return tr[id];
push_down(id);
int mid=(tr[id].l+tr[id].r)>>1;
if (mid>=l&&mid+1<=r) return query(id<<1,l,r)+query(id<<1|1,l,r);
else if (mid>=l) return query(id<<1,l,r);
else if (mid+1<=r) return query(id<<1|1,l,r);
}
void clear() { for (int i=0;i<(N<<2);i++) tr[i].clr(); }
}Tr;
int fa[N],son[N],dep[N],siz[N];
int dfn[N],tme,top[N];
void dfs1(int x,int _fa)
{
fa[x]=_fa,dep[x]=dep[_fa]+1,siz[x]=1;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs1(v,x);
siz[x]+=siz[v];
if (siz[v]>siz[son[x]]) son[x]=v;
}
}
void dfs2(int x,int _top)
{
dfn[x]=++tme,top[x]=_top;
if (!son[x]) return ;
dfs2(son[x],_top);
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==fa[x]||v==son[x]) continue;
dfs2(v,v);
}
}
void update1(int x,int y,int w)
{
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y);
Tr.update(1,dfn[top[x]],dfn[x],w);
x=fa[top[x]];
}
if (dep[x]<dep[y]) swap(x,y);
Tr.update(1,dfn[y],dfn[x],w);
}
int query1(int x,int y)
{
node res1={0,0,0,0,0,0},res2={0,0,0,0,0,0};
bool flag=0;
while (top[x]!=top[y])
{
if (dep[top[x]]<dep[top[y]]) swap(x,y),flag^=1;
if (flag) res2=Tr.query(1,dfn[top[x]],dfn[x])+res2;
else res1=Tr.query(1,dfn[top[x]],dfn[x])+res1;
x=fa[top[x]];
}
if (dep[x]<dep[y]) swap(x,y),flag^=1;
if (flag) res2=Tr.query(1,dfn[y],dfn[x])+res2;
else res1=Tr.query(1,dfn[y],dfn[x])+res1;
return res2.sum+res1.sum+(res1.lc==res2.lc);
}
void clr()
{
for (int i=1;i<=n;i++) fa[i]=son[i]=dep[i]=siz[i]=dfn[i]=top[i]=0;
for (int i=1;i<=n;i++) tr[i].clear();
tme=0;
Tr.clear();
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>T;
while (T--)
{
cin>>n>>m;
for (int i=1,u,v;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v),tr[v].push_back(u);
}
dfs1(1,0),dfs2(1,1);
Tr.build(1,1,n);
int f=n+1,op,a,b;
while (m--)
{
cin>>op>>a>>b;
if (op==1) update1(a,b,++f);
else cout<<query1(a,b)<<"\n";
}
clr();
}
return 0;
}
Day 4
树上启发式合并&线性基
是一种离线思想,一般用于解决子树信息维护问题复杂度
1.1 板子
这不能线段树合并吗?
咕了,这是一个优雅的暴力
1.2 啦啦啦
也是一个优雅的暴力。
对于节点
于是乎,用
因为是从下往上遍历的这棵树,所以
码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5;
const int inf=0x3f3f3f3f;
int n,k;
struct node { int nxt,val; };
vector <node> tr[N];
map <ll,int> mp;
int ans=inf;
int dfn[N],tme,id[N],siz[N],son[N];
int dep[N],dis[N];
void dfs1(int x,int _fa)//预处理出各种信息
{
dep[x]=dep[_fa]+1,siz[x]=1,dfn[x]=++tme,id[tme]=x;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i].nxt,w=tr[x][i].val;
if (v==_fa) continue;
dis[v]=dis[x]+w;
dfs1(v,x);
siz[x]+=siz[v];
if (siz[v]>siz[son[x]]) son[x]=v;
}
}
void update(int x,int k)
{
if (k==-1) mp[dis[x]]=0;
else
{
if (!mp[dis[x]]) mp[dis[x]]=dep[x];
else mp[dis[x]]=min(mp[dis[x]],dep[x]);
}
}
void dfs2(int x,int _fa)
{
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i].nxt;
if (v==_fa||v==son[x]) continue;
dfs2(v,x);
}
if (son[x]) dfs2(son[x],x);
for (int i=0;i<_size;i++)
{
int v=tr[x][i].nxt;
if (v==_fa||v==son[x]) continue;
for (int j=0;j<siz[v];j++)//要先匹配再加点,防止自己和自己匹配
{
int vv=id[dfn[v]+j],ww=k+dis[x]*2-dis[vv];
if (mp[ww]) ans=min(ans,dep[vv]+mp[ww]-dep[x]*2);
}
for (int j=0;j<siz[v];j++) update(id[dfn[v]+j],1);
}
update(x,1);
if (mp[dis[x]+k]) ans=min(ans,mp[dis[x]+k]-dep[x]);
if (x!=son[_fa]) for (int i=0;i<siz[x];i++) update(id[dfn[x]+i],-1);//不是重儿子,自己主动删
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k;
for (int i=1,u,v,w;i<n;i++)
{
cin>>u>>v>>w;
u++,v++;
tr[u].push_back({v,w}),tr[v].push_back({u,w});
}
dfs1(1,0),dfs2(1,0);
if (ans==inf) cout<<-1;
else cout<<ans;
return 0;
}
1.3 天天咱又见面了
把一次路径掰成两半:
对于
所以开两个
警示自己:推式子的时候不要把公共祖先和观测点混为一谈!
嘻
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+5;
int n,m,w[N];
vector <int> tr[N];
struct node { int x,op,val; };
vector <node> p[N];
map <int,int> mp1,mp2;
int ans[N];
int siz[N],dep[N],fa[N][25];
int dfn[N],tme,idx[N],son[N];
void dfs1(int x,int _fa)
{
siz[x]=1,dep[x]=dep[_fa]+1;
dfn[x]=++tme,idx[tme]=x,fa[x][0]=_fa;
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa) continue;
dfs1(v,x);
siz[x]+=siz[v];
if (siz[v]>siz[son[x]]) son[x]=v;
}
}
inline void init()
{
for (int j=1;j<=20;j++)
for (int i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1];
}
inline int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
int delta=dep[x]-dep[y],lg=0;
while (delta)
{
if (delta&1) x=fa[x][lg];
delta>>=1,lg++;
}
if (x==y) return x;
for (int i=20;i>=0;i--)
{
if(fa[x][i]==fa[y][i]) continue;
x=fa[x][i],y=fa[y][i];
}
return fa[x][0];
}
void dfs2(int x,int _fa)
{
int _size=tr[x].size();
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa||v==son[x]) continue;
dfs2(v,x);
mp1.clear(),mp2.clear();
}
if (son[x]) dfs2(son[x],x);
for (int i=0;i<_size;i++)
{
int v=tr[x][i];
if (v==_fa||v==son[x]) continue;
for (int j=0;j<siz[v];j++)
{
int vv=idx[dfn[v]+j];
int size2=p[vv].size();
for (int k=0;k<size2;k++)
{
if (p[vv][k].op==1) mp1[p[vv][k].x]+=p[vv][k].val;
else mp2[p[vv][k].x]+=p[vv][k].val;
}
}
}
int size2=p[x].size();
for (int k=0;k<size2;k++)
{
if (p[x][k].op==1) mp1[p[x][k].x]+=p[x][k].val;
else mp2[p[x][k].x]+=p[x][k].val;
}
ans[x]=mp1[dep[x]+w[x]]+mp2[dep[x]-w[x]];
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for (int i=1,u,v;i<n;i++)
{
cin>>u>>v;
tr[u].push_back(v),tr[v].push_back(u);
}
dfs1(1,0),init();
for (int i=1;i<=n;i++) cin>>w[i];
for (int i=1,s,t;i<=m;i++)
{
cin>>s>>t;
int l=lca(s,t);
p[s].push_back({dep[s],1,1}),p[t].push_back({dep[l]*2-dep[s],2,1});
p[l].push_back({dep[s],1,-1}),p[fa[l][0]].push_back({dep[l]*2-dep[s],2,-1});
}
dfs2(1,0);
for (int i=1;i<=n;i++) cout<<ans[i]<<" ";
return 0;
}
2.1 线性基
线性基是一种很适合处理异或问题的数据结构(这么像数论的一个东西居然是数据结构……)
若原数组空间为
用途:可以快速查询若干个数的最大/最小异或和,第k大异或和,或查询一个数是否可以被若干个数异或得出
总之很神奇的一个东西。
(求排名/给出排名求值待补)
void insert(int x)
{
for (int i=N;i>=0;i--)
{
if (!(x&(1<<i))) continue;
if (!p[i]) { p[i]=x; return ; }
x^=p[i];
}
}
bool check(int x)
{
for (int i=N;i>=0;i--) if (x&(1<<i)) x^=p[i];
return x==0;
}
int query_mx()
{
int res=0;
for (int i=N;i>=0;i--) res=max(res,rse^p[i]);
return res;
}
int query_mn()
{
for (int i=0;i<=N;i++) if (p[i]) return p[i];
return 0;
}
void rebuild()
{
for (int i=N;i>=0;i--)
for (int j=i-1;j>=0;j--) { if (p[i]&(1ll<<j)) p[i]^=p[j]; }
for (int i=0;i<=N;i++) if (p[i]) d[++cnt]=p[i];
}
int rank_k(int k)
{
rebuild();
if (k>=(1ll<<cnt)) return -1;
int res=0;
for (int i=N;i>=0;i--) if (k&(1ll<<i)) res^=d[i];
return res;
}
int query_rk(int x)
{
int res=0;
for (int i=cnt-1;i>=0;i--) if (x>=d[i]) { res+=(1ll<<i); x^=d[i]; }
return res;
}
单调队列&斜率优化&决策单调性
1.单调队列优化
一般和不那么严格的区间选择有关,且区间的左端点是单调的。那么就可以利用题目中的性质用单调队列维护完事儿
根据题目要求判断查询、插入的顺序。一般来讲还是先插入好……
时刻警惕第 0 个状态是否可以刚开始就放
Day 5
网络流
?听不懂
?听不懂一点
一、EK增广路算法
这里的增广路意为从源点到汇点、所有边剩余容量都大于零的一条路径
然后就是暴力
为什么要建反向边呢?因为会有以下情况:本来一条容量很大的边可以容纳较大的流量,但因为其容量被很多小流量占用了,它的大容量就只剩一点了……而那些小流量本来能走小容量的边,但占据了大容量,也就导致没能“物尽其用”。然后待补
码(复杂度过不了)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=2e5+5;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,s,t;
ll dis[2010][2010];
vector <int> e[N];
ll ans;
ll vis[N],pre[N],mn;
inline bool bfs()
{
for (int i=1;i<=n;i++) vis[i]=0;
mn=inf;
queue <int> q;
q.push(s),vis[s]=1;
while (!q.empty())
{
int u=q.front();
q.pop();
int _size=e[u].size();
for (int i=0;i<_size;i++)
{
int v=e[u][i];
if (dis[u][v]>0)
{
if (vis[v]) continue;
mn=min(mn,dis[u][v]);
pre[v]=u;
q.push(v);
vis[v]=1;
if (v==t) return true;
}
}
}
return false;
}
inline void add()
{
int u=t;
while (u!=s)
{
int v=pre[u];
dis[v][u]-=mn,dis[u][v]+=mn;
u=v;
}
ans+=mn;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>s>>t;
for (int i=1,u,v,c;i<=m;i++)
{
cin>>u>>v>>c;
e[u].push_back(v),e[v].push_back(u);
dis[u][v]+=c;
}
while (bfs()) add();
cout<<ans;
return 0;
}
二、Dinic算法
可恶的EK复杂度浪费我感情
然后考虑更新。
当前弧优化:若一条路已经被增广过,那么它就不会被增广第二次,可用来剪枝
码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=210;
const int M=5e3+2;
const int inf=0x3f3f3f3f3f3f3f3f;
int n,m,s,t;
int head[N],ans;
struct node { int to,nxt,val; }e[M<<1];
int cnt;
int dep[N],cur[N];
inline void add(int u,int v,int w)//被迫使用链前
{
e[++cnt].to=v;
e[cnt].val=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
inline bool bfs()//分层
{
queue <int> q;
q.push(s);
for (int i=1;i<=n;i++) dep[i]=inf;
dep[s]=0;
cur[s]=head[s];
while (!q.empty())
{
int u=q.front();
q.pop();
for (int i=head[u];i;i=e[i].nxt)
{
int v=e[i].to;
if (e[i].val>0&&dep[v]==inf)
{
q.push(v);
cur[v]=head[v],dep[v]=dep[u]+1;
if (v==t) return true;
}
}
}
return false;
}
inline int dfs(int x,int sum)
{
if (x==t) return sum;
int k,res=0;
for (int i=cur[x];i&∑i=e[i].nxt)
{
cur[x]=i;
int v=e[i].to;
if (e[i].val>0&&(dep[v]==dep[x]+1))
{
k=dfs(v,min(sum,e[i].val));
if (!k) dep[v]=inf;
e[i].val-=k,e[i^1].val+=k;
res+=k,sum-=k;
}
}
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>s>>t;
for (int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
add(u,v,w),add(v,u,0);
}
while (bfs()) ans+=dfs(s,inf);
cout<<ans;
return 0;
}
矩阵
矩阵
矩阵加法:
矩阵减法:加法的逆运算,对应位置元素相减即可
矩阵乘法:
2.1 矩阵快速幂
和普通的快速幂过程一样,但是在快速幂过程中的乘法是矩阵乘
贴
#incIude <bits/stdc++.h>
#define int long long
using namespace std;
const int N=101;
const int MOD=1e9+7;
int n,k;
struct node{
int a[N][N];
node() { memset(a,0,sizeof a); }
inline void build() { for (int i=1;i<=n;i++) a[i][i]=1; }
}a;
node ans;
node operator *(const node &x,const node &y)
{
node res;
for (int k=1;k<=n;k++)
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) res.a[i][j]=(res.a[i][j]+x.a[i][k]*y.a[k][j]%MOD)%MOD;
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++) cin>>a.a[i][j];
ans.build();//矩阵快速幂最开始都要这样初始化
while (k)
{
if (k&1) ans=ans*a;
a=a*a,k>>=1;
}
for (int i=1;i<=n;i++)
{
for (int j=1;j<=n;j++) cout<<ans.a[i][j]<<" ";
cout<<"\n";
}
return 0;
}
2.2 斐波那契数列
手推了个寂寞。乖乖去看题解了
不知道为啥,题解想到了用矩阵加速递推速度。对于一个矩阵
根据定义手推后发现这个
tie码
#inciude <bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=1e9+7;
int n;
struct node
{
int a[5][5];
node() { memset(a,0,sizeof a); }
void build() { a[1][1]=a[1][2]=a[2][1]=1; a[2][2]=0; }
}a;
node ans;
node operator *(const node &x,const node &y)
{
node res;
for (int k=1;k<=2;k++)
for (int i=1;i<=2;i++)
for (int j=1;j<=2;j++) res.a[i][j]=(res.a[i][j]+x.a[i][k]*y.a[k][j]%MOD)%MOD;
return res;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
if (n==1||n==2) { cout<<1; return 0; }
ans.build();
a.a[1][1]=a.a[1][2]=1;
n-=2;
do{
if (n&1) a=a*ans;
ans=ans*ans,n>>=1;
}while (n);
cout<<a.a[1][1];
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效