2021湖南多校对抗赛第三场

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 1n200

题解:找环。如果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 1n2e5,0k1e9

题解:思维题,打个表观察一下就会发现如下的性质:

  1. 如果最左边的字符是1,小球被弹回,最左边的字符变成0。
  2. 如果最左边的字符是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 1n1e5,1ai,x,y1e18

题解:DP。首先有一个O(n2)的DP比较显然:

  1. 排序。用DPA(i,j)表示前i个数字中,第i个数字在集合A,集合B中最大的数字是a[j]的方案数。同理,DPB(i,j)表示前i个数字中,第i个数字在集合B,集合A中最大数字是a[j]的方案数。

  2. 转移方程也很好列:

    考虑第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[i1][j]     (a[i]a[i1]>=x)dpb[i][j]+=dpb[i1][j]     (a[i]a[i1]>=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][i1]+=j=0i1dpb[i1][j]     (a[i]a[j]>=x)dpb[i][i1]+=j=0i1dpa[i1][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;
    }
    
  3. 考虑优化,空间上首先需要滚动数组。那么第一个转移方程就变成了一个区间赋值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;
    }
    
  4. 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 1n,A,vi,pi2000,1B1e9

题解:二分天数,假设需要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+szxkx

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) (ji)%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) jidp(j),其中 j 与 i 同 奇 偶 j与i同奇偶 ji,则有

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(i1),s(i)=s(i2)+dp(i)

得到 s ( i ) = s ( i − 1 ) + s ( i − 2 ) s(i)=s(i-1)+s(i-2) s(i)=s(i1)+s(i2) i i i是可以跳的格子的情况)

i i i是不可以跳的格子的时候, s ( i ) = s ( i − 2 ) s(i)=s(i-2) s(i)=s(i2)

那么你可以发现,在一段连续的可以跳的区间, s s s的递推式是可以用矩阵快速幂加速的,而遇到一个障碍物的时候,其实相当于交换递推式第一项和第二项的值,再去进行下一轮的递推。为了方便,我们可以设置 n + 1 n+1 n+1也是一个不可以跳的点,然后分段的进行矩阵快速幂,最后得到 s ( n ) s(n) s(n) s ( n − 1 ) s(n-1) s(n1), 其中 s ( n − 1 ) s(n-1) s(n1)即为答案(需要特判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;
}

猜你喜欢

转载自blog.csdn.net/m0_56366907/article/details/115431086