2021湖南多校对抗赛第三场
排名
第一 | 第二 | 第三 |
---|---|---|
团体成绩
学校 | 总题数 | 总罚时 |
---|---|---|
题解(部分)
special thanks: Binbin,验了BDGL,出了两道防AK题,太爹了%%%。
预测难度: K < L < G < F < C < E < I < A < B < D < J < H K\lt L \lt G \lt F \lt C \lt E \lt I \lt A \lt B \lt D \lt J \lt H K<L<G<F<C<E<I<A<B<D<J<H
实际难度: K < E < C < G < L < F < A < I < B D J H K\lt E \lt C \lt G \lt L \lt F \lt A \lt I \lt BDJH K<E<C<G<L<F<A<I<BDJH
几道原创题没人写,sad
写两个好写的非原创题题解+所有原创题题解。其它题题解可以在CF上找到(因为懒)。
F
比较意外的F题,实际CF难度只有1700。
题意:给n个结点的图,给出每个点的父结点。问最多修改几条边,让它们形成一棵树。
题解:实际上只需要干两件事。如果图中没有环,那么一定是一个森林,只需要随意指定一个树的根,让每一个树的根都向那个根连边就好了。如果有环其实差不多,只需要拆一条边出来,往根结点连即可。并查集维护。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int fa[maxn];
int f[maxn];
int n,rt;
int ans;
int find(int x) {return x==f[x]?x:f[x]=find(f[x]);}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",fa+i);
if(fa[i]==i) rt=i;
f[i]=i;
}
for(int i=1;i<=n;i++)
{
if(i==rt) continue;
int u=find(i),v=find(fa[i]);
if(u==v) //成环
{
if(rt==0) rt=i;
f[u]=fa[i]=rt;
++ans;
}
else f[u]=v;
}
printf("%d\n",ans);
for(int i=1;i<=n;i++) printf("%d ",fa[i]);
return 0;
}
I
题意:给出一个有向图,每一个点只有一个出边。求一个最小的k,使得不论从哪个点u出发,走k步后到v,再从v走k步后还在v。 1 ≤ n ≤ 200 1 \le n \le 200 1≤n≤200
题解:找环。如果v在某一个环内,环的长度为|L|,那么k如果是|L|的倍数,走k步后一定会回到v。那么如果k是所有环长度的lcm,不管从哪个环的哪个点开始走k步,都会回到原点。此外,注意这个k还需要使得不管从哪个点开始走,走k步后一定在某一个环内。
#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=5005;
const int mod=1e9+7;
int n;
int a[maxn],d[maxn];
bool vis[maxn],tag[maxn];
vector<int>sz;
vector<int>path;
void dfs(int u)
{
if(vis[u]) return;
path.push_back(u);
vis[u]=1;
dfs(a[u]);
}
void dfs2(int u,int &len)
{
if(tag[u]) return;
dfs2(a[u],++len);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",a+i);
for(int i=1;i<=n;i++)
{
memset(vis,0,sizeof(vis));
path.clear();
dfs(i);
if(a[path.back()]==i) //从i回到i,成环。
{
for(auto u:path) tag[u]=1;
sz.push_back(path.size());
}
}
ll ans=sz[0];
for(int i=1;i<sz.size();i++)
{
ll t=__gcd(ans,1ll*sz[i]);
ans=ans/t*sz[i];
}
int mx=0;
for(int i=1;i<=n;i++)
{
int len=0;
dfs2(i,len);
mx=max(mx,len);
}
ll res=ans;
while(res<mx) res+=ans;
cout<<res<<endl;
return 0;
}
B
题意:给一个01串,小球遇到0时能穿过,遇到1时被弹回,每次小球经过一个字符后会把字符反转。给出Q个询问,每次询问给出 k i k_i ki,问当一个个地放入 k i k_i ki个小球时,最后01串的结果。 1 ≤ n ≤ 2 e 5 , 0 ≤ k ≤ 1 e 9 1\le n\le 2e5, 0\le k\le 1e9 1≤n≤2e5,0≤k≤1e9
题解:思维题,打个表观察一下就会发现如下的性质:
- 如果最左边的字符是1,小球被弹回,最左边的字符变成0。
- 如果最左边的字符是0,原来[2,n]的01串会全部取反后变为新串的[1,n-1]位置,新串的最后一个位置是1。
这样一来,可以发现最多放入4个球,串的末尾就会多出一个01,那么最多放入2n个球,原串就会变为01交替的串。模拟这个过程即可。时间复杂度O(n)。
#include<bits/stdc++.h>
#define ll long long
#define all(x) (x).begin(),(x).end()
#define P pair<int,int>
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1e6+5;
const int mod=998244353;
const int inv2=(mod+1)/2;
int a[maxn];
int n;
int fac[maxn];
int ans[maxn];
int main()
{
fac[0]=1;
for(int i=1;i<maxn;i++) fac[i]=fac[i-1]*2%mod;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%1d",a+i);
int sum=0,all=(fac[n-1]-1)*2%mod;
for(int i=1;i<n;i++) if(a[i]) sum=(sum+fac[i])%mod;
int p=0;bool rev=false;
int tmp=n;
for(int i=0;i<=2*n+1;i++)
{
int now=rev^a[p];
ans[i]=(sum+now)%mod;
if(now==1)
{
if(rev) a[p]=1;
else a[p]=0;
}
else
{
rev^=1;
if(rev) a[tmp++]=0;
else a[tmp++]=1;
sum=(1ll*(all-sum)*inv2%mod+fac[n-1])%mod;
sum=(sum-(a[++p]^rev)+mod)%mod;
}
}
int Q,k;
scanf("%d",&Q);
while(Q--)
{
scanf("%d",&k);
k=min(k,2*n+(k&1));
printf("%d\n",ans[k]);
}
return 0;
}
D
题意:给n个正整数,两两不同。要把这n个数分为两个集合,使得集合A中任意两数之差不小于x,集合B中任意两数之差不小于y,问方案数。 1 ≤ n ≤ 1 e 5 , 1 ≤ a i , x , y ≤ 1 e 18 1\le n \le 1e5,1\le a_i,x,y \le 1e18 1≤n≤1e5,1≤ai,x,y≤1e18
题解:DP。首先有一个O(n2)的DP比较显然:
-
排序。用DPA(i,j)表示前i个数字中,第i个数字在集合A,集合B中最大的数字是a[j]的方案数。同理,DPB(i,j)表示前i个数字中,第i个数字在集合B,集合A中最大数字是a[j]的方案数。
-
转移方程也很好列:
考虑第i个数字放的位置。如果放在和i-1同一个集合:
d p a [ i ] [ j ] + = d p a [ i − 1 ] [ j ] ( a [ i ] − a [ i − 1 ] > = x ) d p b [ i ] [ j ] + = d p b [ i − 1 ] [ j ] ( a [ i ] − a [ i − 1 ] > = y ) dpa[i][j]+=dpa[i-1][j]\ \ \ \ \ (a[i]-a[i-1]>=x) \\ dpb[i][j]+=dpb[i-1][j]\ \ \ \ \ (a[i]-a[i-1]>=y)\\ dpa[i][j]+=dpa[i−1][j] (a[i]−a[i−1]>=x)dpb[i][j]+=dpb[i−1][j] (a[i]−a[i−1]>=y)
如果放在不同的集合:
d p a [ i ] [ i − 1 ] + = ∑ j = 0 i − 1 d p b [ i − 1 ] [ j ] ( a [ i ] − a [ j ] > = x ) d p b [ i ] [ i − 1 ] + = ∑ j = 0 i − 1 d p a [ i − 1 ] [ j ] ( a [ i ] − a [ j ] > = y ) dpa[i][i-1]+=\sum_{j=0}^{i-1}dpb[i-1][j]\ \ \ \ \ (a[i]-a[j]>=x)\\ dpb[i][i-1]+=\sum_{j=0}^{i-1}dpa[i-1][j]\ \ \ \ \ (a[i]-a[j]>=y) dpa[i][i−1]+=j=0∑i−1dpb[i−1][j] (a[i]−a[j]>=x)dpb[i][i−1]+=j=0∑i−1dpa[i−1][j] (a[i]−a[j]>=y)
那么答案就是 ∑ d p a [ n ] [ i ] + d p b [ n ] [ i ] \sum dpa[n][i]+dpb[n][i] ∑dpa[n][i]+dpb[n][i]。代码如下:#include<bits/stdc++.h> #define ll long long using namespace std; const int maxn=5005; const int mod=1e9+7; int n; ll x,y; ll a[maxn],dpa[maxn][maxn],dpb[maxn][maxn]; int main() { scanf("%d%lld%lld",&n,&x,&y); for(int i=1;i<=n;i++) scanf("%lld",a+i); sort(a+1,a+1+n); a[0]=-1e18; dpa[0][0]=1; for(int i=1;i<=n;i++) { for(int j=0;j<i;j++) { if(a[i]-a[i-1]>=x) dpa[i][j]=(dpa[i][j]+dpa[i-1][j])%mod; if(a[i]-a[i-1]>=y) dpb[i][j]=(dpb[i][j]+dpb[i-1][j])%mod; } ll sum=0; ll sum2=0; for(int j=0;j<i;j++) { if(a[i]-a[j]>=x) sum=(sum+dpb[i-1][j])%mod; if(a[i]-a[j]>=y) sum2=(sum2+dpa[i-1][j])%mod; } dpa[i][i-1]=(dpa[i][i-1]+sum)%mod; dpb[i][i-1]=(dpb[i][i-1]+sum2)%mod; } ll ans=0; for(int i=0;i<=n;i++) ans+=dpa[n][i]+dpb[n][i]; cout<<ans%mod<<endl; return 0; }
-
考虑优化,空间上首先需要滚动数组。那么第一个转移方程就变成了一个区间赋值0的操作:对dpa,区间[j+1,i]变为0,其中j是满足a[i]-a[j]>=x的最大j。同理对dpb。第二个转移方程就变成了一个区间求和操作。这两个操作都可以用线段树维护,空间复杂度O(n),时间复杂度O(nlogn)。代码如下:
#include<bits/stdc++.h> #define ll long long #define lson rt<<1 #define rson rt<<1|1 using namespace std; const int maxn=1e5+5; const int mod=1e9+7; int n; ll x,y; ll a[maxn]; struct nod { ll val,tag; nod() {val=0;tag=-1;} }tra[maxn<<2],trb[maxn<<2]; void up(int rt,nod *tr) {tr[rt].val=(tr[lson].val+tr[rson].val)%mod;} void down(int l,int r,int rt,nod *tr) { if(tr[rt].tag!=-1) { int m=l+r>>1; tr[lson].val=(m-l+1)*tr[rt].tag%mod; tr[rson].val=(r-m)*tr[rt].tag%mod; tr[lson].tag=tr[rson].tag=tr[rt].tag; tr[rt].tag=-1; } } void update(int l,int r,int rt,int L,int R,ll v,nod *tr) { if(L<=l&&R>=r) {tr[rt].val=1ll*(r-l+1)*v%mod;tr[rt].tag=v;return;} down(l,r,rt,tr); int m=l+r>>1; if(L<=m) update(l,m,lson,L,R,v,tr); if(m<R) update(m+1,r,rson,L,R,v,tr); up(rt,tr); } ll qry(int l,int r,int rt,int L,int R,nod *tr) { if(L<=l&&R>=r) return tr[rt].val; down(l,r,rt,tr); int m=l+r>>1; ll res=0; if(L<=m) res+=qry(l,m,lson,L,R,tr); if(m<R) res+=qry(m+1,r,rson,L,R,tr); up(rt,tr); return res%mod; } int main() { scanf("%d%lld%lld",&n,&x,&y); for(int i=1;i<=n;i++) scanf("%lld",a+i); sort(a+1,a+1+n); a[0]=-1e18; update(1,n+1,1,1,1,1,tra); for(int i=1;i<=n;i++) { int pa=upper_bound(a+1,a+1+n,a[i]-x)-a; int pb=upper_bound(a+1,a+1+n,a[i]-y)-a; ll sum=qry(1,n+1,1,1,pa,trb); ll sum2=qry(1,n+1,1,1,pb,tra); if(a[i]-a[i-1]<x) update(1,n+1,1,1,n+1,0,tra); if(a[i]-a[i-1]<y) update(1,n+1,1,1,n+1,0,trb); ll ax=qry(1,n+1,1,i,i,tra); ll bx=qry(1,n+1,1,i,i,trb); update(1,n+1,1,i,i,(ax+sum)%mod,tra); update(1,n+1,1,i,i,(bx+sum2)%mod,trb); } ll ans=tra[1].val+trb[1].val; cout<<ans%mod<<endl; return 0; }
-
Binbin则直接发现了一个O(n)的解法(oh,请%斌姐姐)。因为实际上删除操作对应的区间左端点是单调增的,所以不需要线段树,只需要类似尺取法的方法暴力更新即可。
G
题意:你要给斌姐姐买女装。女装价值B元,但是你现在只有A元。有n件物品可以买,第i件物品价格 v i v_i vi,每天能带来 p i p_i pi的收益。只能在第一天买物品,问至少需要几天能凑够B元。 1 ≤ n , A , v i , p i ≤ 2000 , 1 ≤ B ≤ 1 e 9 1 \le n,A,v_i,p_i\le 2000,1\le B \le 1e9 1≤n,A,vi,pi≤2000,1≤B≤1e9
题解:二分天数,假设需要x天,那么如果你买了物品i,x天后能带来收益 x p i xp_i xpi。之后就是一个01背包问题,直接DP就没了。实际上可以不用二分,直接背包。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2005;
int A,B;
int n;
int w[maxn];
ll v[maxn];
int b[maxn];
ll dp[maxn][maxn];
bool check(int x)
{
for(int i=1;i<=n;++i) v[i]=1ll*b[i]*x;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;++i)
{
for(int j=0;j<=A;++j)
{
dp[i][j]=dp[i-1][j];
if(j>=w[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-w[i]]+v[i]);
}
}
for(int i=0;i<=A;i++) if(dp[n][i]+(A-i)>=B) return true;
return false;
}
int main()
{
scanf("%d",&n);
scanf("%d%d",&A,&B);
for(int i=1;i<=n;i++) scanf("%d%d",w+i,b+i);
int l=0,r=1e9,mid,ans=0;
while(l<=r)
{
mid=l+r>>1;
if(check(mid))
{
ans=mid;
r=mid-1;
}
else l=mid+1;
}
printf("%d\n",ans);
return 0;
}
L
oh这题和昆明区域赛K撞的差不多了。实际上L是我们早就出好的,雷同纯属巧合。
题意:给一副13张牌的单花色麻将手牌,问听牌。
题解:注意到雀头只有一个,枚举雀头是哪个,然后剩下的就是判断面子。因为麻将牌每个数不会超过4张,所以对每一张牌,能够构成刻子(3张相同)就成刻子,不能的话就构成顺子,绝对是最优解。模拟。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5005;
const int mod=1e9+7;
int n;
int cnt[20];
set<int>ans;
bool ok;
void dfs(int p)
{
if(p==10) {ok=true;return;}
if(cnt[p]>=3)
{
cnt[p]-=3;
dfs(p);
cnt[p]+=3;
}
else
{
if(cnt[p+1]>=cnt[p]&&cnt[p+2]>=cnt[p])
{
int tmp=cnt[p];
cnt[p]-=tmp;
cnt[p+1]-=tmp;
cnt[p+2]-=tmp;
dfs(p+1);
cnt[p]+=tmp;
cnt[p+1]+=tmp;
cnt[p+2]+=tmp;
}
}
}
char s[20];
int main()
{
int T;
scanf("%d",&T);
for(int cas=1;cas<=T;cas++)
{
scanf("%s",s);
memset(cnt,0,sizeof(cnt));
ans.clear();
for(int i=0;s[i];i++) cnt[s[i]-'0']++;
for(int i=1;i<10;i++)
{
cnt[i]++;
for(int j=1;j<10;j++)
{
ok=false;
if(cnt[j]>=2)
{
cnt[j]-=2;
dfs(1);
cnt[j]+=2;
}
if(ok) break;
}
cnt[i]--;
if(ok) ans.insert(i);
}
printf("Case #%d:\n",cas);
cout<<ans.size()<<endl;
for(auto x:ans) cout<<x<<' ';puts("");
}
return 0;
}
Update
H
因为被Ms. Ze钓鱼搞的QQ封号,差点想把这题删了…
考虑对于单个查询,二分答案,要检查一个答案x是否可行,那么问题就变成:
有x个容器,每个容器装的球不可以相同,一个容器最多装k个球,问是否可以把所有容器装满
对于出现次数 >=x的球,直接每个容器放一个,出现次数<x的球平铺,则可以装满的条件是 s u m + s z ∗ x ≤ k ∗ x sum+sz*x\le k*x sum+sz∗x≤k∗x
s u m sum sum是所有 <= 的ai的和,sz是 >=ai的数字的个数
众所周知主席树可以处理出一个区间对应权值范围的信息。因为它带修,所以需要树状数组套主席树。
在线段树上二分来优化掉一个log,复杂度 n l o g 2 n nlog^2n nlog2n
数据结构部分比较套路,适合作为树套树入门题。
每当我写树套树上二分的时候都会想起学弟的金玉良言:
所以这题也可以用整体二分愉快的A掉,时间和空间都完爆树套树。
树套树做法:
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
#define all(vec) vec.begin(),vec.end()
using namespace std;
const int maxn = 1e5 + 5;
const ll N = 1e12;
int T[maxn];
int lc[maxn*500], rc[maxn*500], sz[maxn*500], tot = 0;
ll sum[maxn*500];
void update(int &rt, ll l, ll r, ll pos, int x){
if(!rt) rt = ++tot, assert(tot < maxn*500);
sz[rt]+=x; sum[rt] += (ll)x*pos;
if(l==r) return;
if(pos <= mid) update(lc[rt],l,mid,pos,x);
else update(rc[rt],mid+1,r,pos,x);
return;
}
int n, m, a[maxn];
void add_bit(int pos, ll val, int d){
while(pos <= n){
update(T[pos],1,N,val,d); pos += lowbit(pos);
}
}
void sol(int x, int y, int k){
vector<int> t1, t2; t1.clear(); t2.clear();
vector<int> tmp;
x--;
ll l = 1, r = N;
while(y) t1.pb(T[y]), y -= lowbit(y);
while(x) t2.pb(T[x]), x -= lowbit(x);
ll res_sum = 0, res_sz = 0;
while(l < r){
ll lsum = 0, rsz = 0;
for(int x:t1) lsum += sum[lc[x]], rsz += sz[rc[x]];
for(int x:t2) lsum -= sum[lc[x]], rsz -= sz[rc[x]];
ll sum = lsum+res_sum, sz = rsz+res_sz;
ll lim = mid+1;
if(sum+sz*lim >= lim*k){
tmp.clear();
for(int x:t1) if(rc[x]) tmp.pb(rc[x]);
swap(tmp,t1);
tmp.clear();
for(int x:t2) if(rc[x]) tmp.pb(rc[x]);
swap(tmp,t2); l = mid+1; res_sum = sum;
}else{
tmp.clear();
for(int x:t1) if(lc[x]) tmp.pb(lc[x]);
swap(tmp,t1);
tmp.clear();
for(int x:t2) if(lc[x]) tmp.pb(lc[x]);
swap(tmp,t2); r = mid; res_sz = sz;
}
}
printf("%lld\n", l);
}
void change(int x, int y){
if(a[x] == y) return;
add_bit(x, a[x], -1);
add_bit(x, a[x]=y, 1);
}
int main()
{
cin>>n>>m;
fors(i,1,n+1){
scanf("%d", &a[i]); add_bit(i, a[i], 1);
}
while(m--){
int op; scanf("%d", &op);
if(op == 1) {
int l, r, k;
scanf("%d%d%d", &l, &r, &k);
sol(l, r, k);
}
else {
int x, y; scanf("%d%d", &x, &y);
change(x, y);
}
}
return 0;
}
整体二分:
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
#define all(vec) vec.begin(),vec.end()
using namespace std;
const int maxn = 5e5 + 5;
const ll N = 1e12;
struct node{
int op;//1,-1,0
int l, r;//pos val
ll sz, sum;
int id, k;//
}e[maxn], q1[maxn], q2[maxn];
int n, m, tot=0;
ll sz[maxn], sum[maxn];
void add(ll a[], int i, ll x){
while(i<=n) a[i] += x, i += lowbit(i);
}
ll qry(ll a[], int i){
ll res=0; while(i) res+=a[i], i -= lowbit(i); return res;
}
ll ans[maxn];
void sol(ll l, ll r, int L, int R){
if(l > r || L > R) return;
int p1 = 0, p2 = 0;
fors(i,L,R+1){
if(e[i].op){
if(e[i].r <= mid) add(sum, e[i].l, e[i].op*e[i].r),q1[p1++]=e[i];
else add(sz, e[i].l, e[i].op), q2[p2++]=e[i];
}else{
ll csz = qry(sz, e[i].r)-qry(sz, e[i].l-1) + e[i].sz;
ll csum = qry(sum, e[i].r)-qry(sum,e[i].l-1) + e[i].sum;
if((ll)e[i].k*mid <= csum + csz*mid){
ans[e[i].id] = mid;
e[i].sum = csum;
q2[p2++] = e[i];
}else{
e[i].sz = csz;
q1[p1++] = e[i];
}
}
}
int o = L;
fors(i,0,p1) {
e[o++] = q1[i];
if(q1[i].op) add(sum, q1[i].l, -q1[i].op*q1[i].r);
}
fors(i,0,p2) {
e[o++] = q2[i];
if(q2[i].op) add(sz, q2[i].l, -q2[i].op);
}
assert(qry(sum,n) == 0 && qry(sz, n) == 0);
sol(l, mid-1, L, L+p1-1);
sol(mid+1,r,L+p1,R);
return;
}
int a[maxn];
int main()
{
cin>>n>>m;
fors(i,1,n+1){
int x; scanf("%d", &x);a[i]=x;
e[tot].op = 1; e[tot].l = i; e[tot].r = x; tot++;
}
fors(i,0,m){
int op; scanf("%d", &op);
if(op == 1){
int l,r,k;
scanf("%d%d%d", &l, &r, &k);
e[tot].op = 0; e[tot].l=l; e[tot].r = r; e[tot].id = i; e[tot].k = k; tot++;
}else{
ans[i] = -10086;
int x, y; scanf("%d%d", &x, &y);
e[tot].op = -1; e[tot].l = x; e[tot].r = a[x]; tot++;
e[tot].op = 1; e[tot].l = x; e[tot].r = (a[x]=y); tot++;
}
}
sol(1, N, 0, tot-1);
fors(i,0,m){
if(ans[i]==-10086) continue;
printf("%lld\n", ans[i]);
}
return 0;
}
J
好想看Ms. Rain穿JK啊
每次可以往上跳奇数格,有m个格子是不能跳的,问方案数
很容易有一个O(n)的dp:
d p ( i ) = ∑ d p ( j ) 其 中 ( j − i ) % 2 = = 1 dp(i)=\sum dp(j) 其中\ (j-i)\%2==1 dp(i)=∑dp(j)其中 (j−i)%2==1
当i上是障碍物则 d p ( i ) = 0 dp(i)=0 dp(i)=0,那么我们用 s ( i ) s(i) s(i)表示 ∑ j ≤ i d p ( j ) \sum_{j\le i} dp(j) ∑j≤idp(j),其中 j 与 i 同 奇 偶 j与i同奇偶 j与i同奇偶,则有
d p ( i ) = s ( i − 1 ) , s ( i ) = s ( i − 2 ) + d p ( i ) dp(i)=s(i-1), s(i)=s(i-2)+dp(i) dp(i)=s(i−1),s(i)=s(i−2)+dp(i)
得到 s ( i ) = s ( i − 1 ) + s ( i − 2 ) s(i)=s(i-1)+s(i-2) s(i)=s(i−1)+s(i−2)( i i i是可以跳的格子的情况)
当 i i i是不可以跳的格子的时候, s ( i ) = s ( i − 2 ) s(i)=s(i-2) s(i)=s(i−2)。
那么你可以发现,在一段连续的可以跳的区间, s s s的递推式是可以用矩阵快速幂加速的,而遇到一个障碍物的时候,其实相当于交换递推式第一项和第二项的值,再去进行下一轮的递推。为了方便,我们可以设置 n + 1 n+1 n+1也是一个不可以跳的点,然后分段的进行矩阵快速幂,最后得到 s ( n ) s(n) s(n)和 s ( n − 1 ) s(n-1) s(n−1), 其中 s ( n − 1 ) s(n-1) s(n−1)即为答案(需要特判n不可以跳的情况)
至此,我们就可以欣赏雨姐姐穿JK的样子了(
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define lowbit(x) ((x)&(-(x)))
#define mid ((l+r)>>1)
#define lson rt<<1, l, mid
#define rson rt<<1|1, mid+1, r
#define fors(i, a, b) for(int i = (a); i < (b); ++i)
using namespace std;
const int N = 2;
const int mod = 1e9 + 7;
struct mt{
int a[N][N];
mt(){memset(a,0,sizeof a);}
void E(){fors(i,0,N) a[i][i] = 1;}
mt operator * (mt A){
mt ans;
fors(i, 0, N) fors(k, 0, N) if(a[i][k]) fors(j, 0, N)
ans.a[i][j] = (ans.a[i][j] + ((ll)a[i][k] * A.a[k][j])%mod )%mod;
return ans;
}
void show(){
fors(i, 0, N) {fors(j, 0, N) cout<<a[i][j]<<" "; cout<<endl;}
}
};
mt qm(mt A, ll b){
mt res; res.E();
while(b){
if(b&1) res = res*A;
A = A*A;
b >>= 1;
}return res;
}
int solve(ll n, int m, vector<ll>& a) {
assert(n > 0 && n <= 1e18);
assert(m > 0 && m <= 1e5);
assert(a.size() == m);
for(ll x:a) assert(x <= n && x > 0);
a.pb(n+1);
sort(a.begin(), a.end());
a.erase(unique(a.begin(),a.end()), a.end());
assert(a[0] > 0 && a[m-1] <= n);
fors(i, 0, a.size()) if(a[i] == n) return 0;
mt A, B;
A.a[0][0] = A.a[0][1] = A.a[1][0] = 1;
int f[2] = {1, 0};
ll pre = 0;
fors(i, 0, a.size()){
ll num = a[i] - pre - 1;
B = qm(A, num);
int t0 = ((ll)B.a[0][0] * f[0]%mod + (ll)B.a[0][1] * f[1]%mod)%mod;
int t1 = ((ll)B.a[1][0] * f[0]%mod + (ll)B.a[1][1] * f[1]%mod)%mod;
f[0] = t1; f[1] = t0;
pre = a[i];
}
return f[0];
}
vector<ll> v;
int main()
{
ll n; int m; cin>>n>>m;
fors(i, 0, m){ll x; scanf("%lld", &x); v.pb(x);}
cout<<solve(n, m, v)<<endl;
}