2022“杭电杯”中国大学生算法设计超级联赛(4)

2 篇文章 0 订阅
2 篇文章 0 订阅

1007 Climb Stairs (贪心 区间最值)

Climb Stairs

题意:

  • T T T组数据,每组数据有 n n n个怪分别在 1 − n 1-n 1n位置上,生命值分别为 a i a_{i} ai,你现在位于 0 0 0位置上,有攻击力 a 0 a_{0} a0

  • 你可以选择从当前位置往后跳 1 − k 1-k 1k步或者往前退一步,只有当你的攻击力不小于目标位置上的怪物血量,你才可以跳过去。且已经走过的位置不能再走。

  • 跳过去干掉怪物后获得对应血量的攻击力,求能不能干掉所有怪物。

  • ( 1 ≤ n , k ≤ 1 0 5 , 1 ≤ a 0 ≤ 1 0 9 ) (1≤n,k≤10^5 , 1 ≤a _0≤10 ^9) (1n,k1051a0109)
    The sum of n does not exceed 1 0 6 10^6 106.
    分析:

  • 如果前面的怪比你的攻击力高,你只能选择跳过去这个怪,根据题意,那么跳完之后你必须从这个点再往后退,否则这个怪就再也打不了了。

  • 因此可以从小到大枚举k,从大到小的话可能会出现虽然跨过去的这些怪都打完了,但是再起跳的话,k的选择就变得小了,但是从小到大枚举k,能跳就跳的话一定是最优的。
    选择从位置 i i i可以跳到位置 i + l e n i+len i+len后,快速判断出这段区间里的怪能否被打败可以用 后缀和 + + +区间最值 判断出来,求区间最值可以用线段树,但是因为是一段静态区间,可以用 R M Q RMQ RMQ来求出。复杂度 O ( N + N l o g N ) O(N+NlogN) O(N+NlogN)

	#include<iostream>
	#include<cstring>
	#include<algorithm>
	#include<vector>
	#include<cmath>
	#define x first
	#define y second
	using namespace std;
	const int N=1e5+10,M=18;
	int a[N];
	long long sum[N],w[N];
	long long f[N][M];
	int n;
	void init()
	{
	    for(int i=n;i>=0;i--)
	    {
	        sum[i]=sum[i+1]+a[i];
	        w[i]=a[i]-sum[i+1];
	    }
	    for(int j=0;j<M;j++)
	    {
	        for(int i=1;(1<<j)<=n-i+1;i++)
	            if(!j) f[i][j]=w[i];
	            else f[i][j]=max(f[i][j-1],f[i+(1<<j-1)][j-1]);
	    }
	}
	long long RMQ(int l,int r)
	{
	    int len=r-l+1;
	    int k=log(len)/log(2);
	    return max(f[l][k],f[r-(1<<k)+1][k]);
	}
	void solve()
	{
	    long long start;
	    int k;
	    cin>>n>>start>>k;
	    //a[0]=start;
	    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	    init();
	    bool flag=true;
	    //cout<<RMQ(1,7);
	    int last=k;
	    for(int i=0;i<=n;i++)
	    {
	        //if(i==3) cout<<"i==3 "<<start<<endl;
	        //if(i==4) cout<<"i==4 "<<start<<endl;
	        if(i==n)//到最后一个
	        {
	            if(start<a[i]) flag=false;
	            break;
	        }
	        if(start>=a[i+1])//下一个可以到
	            start+=a[i+1],last=k;
	        else//下一个不能到,需要跳
	        {
	            int fl=0;
	            for(int len=2;len<=min(k,last)&&i+len<=n;len++)
	            {
	                int l=i,r=i+len;

	                if(a[r]<=start) //跳到的点可以到
	                {
	                    long long tmp=start+a[r];
	                    l++,r--;
	                    long long maxn=RMQ(l,r);
	                    maxn+=sum[r+1];
	                   // cout<<l<<"  "<<r<<" "<<tmp<<" "<<maxn<<endl;
	                    if(maxn<=tmp)
	                    {

	                      //  cout<<l<<" "<<r<<" "<<i<<" ";
	                        fl=1;
	                        start=tmp+sum[l]-sum[r+1];
	                        last=k-len+1;//下一次能跳到的范围小了
	                       // cout<<i<<" "<<last<<" ";
	                        i=i+len-1;
	                        //cout<<i<<endl;
	                        break;
	                    }
	                }
	            }
	            if(!fl)
	            {
	                flag=false;
	                break;
	            }
	        }
	    }
	    puts(flag?"YES":"NO");
	}
	int main()
	{
	    int t;
	    t=1;
		cin>>t;
		for(int i=1;i<=t;i++)
		{
			solve();
		}
	    return 0;
	}



1011 Link is as bear (结论 线性基)

Link is as bear

题意:

  • T T T组数据,每组给出一个长为n的数组,每次可以令一个区间[l,r]的值都变成整个区间异或后的值。题目中保证 1 − n 1-n 1n中存在某个数至少出现两次。
  • 可以操作无数次。最后要求 1 − n 1-n 1n的值都相同且最大,求这个最大值。

分析:
是个思维+结论题,可以证明,从这 个数里任取一些数异或起来的方案,都是可以构造出对应的操作来
做到的。所以,问题完全等价于给n个数,从中选一些数,使得这些数的异或和最大。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#define x first
#define y second
using namespace std;
typedef long long ll;
ll p[64];
void ins(ll x)
{
    for(int i=62;i>=0;i--)
    {
        if(x>>i&1)
        {
            if(!p[i])
            {
                p[i]=x;
                break;
            }
            else x^=p[i];
        }
    }
}
ll askmax()
{
    ll res=0;
    for(int i=62;i>=0;i--) res=max(res,(res^p[i]));
    return res;
}
void solve()
{
    int n;
    cin>>n;
    for(int i=0;i<64;i++) p[i]=0;
    while(n--)
    {
        ll x;
        scanf("%lld",&x);
        ins(x);
    }
    cout<<askmax()<<endl;
}
int main()
{
    int t;
    t=1;
    cin>>t;
    for(int i=1;i<=t;i++)
    {
        solve();
    }
    return 0;
}

1001 Link with Bracket Sequence II 区间DP

Link with Bracket Sequence II

题意:

  • 给出一个长度为 n n n的括号序列,由 m m m种括号构成;
  • 其中 a [ i ] > 0 a[i]>0 a[i]>0表示左括号, a [ i ] < 0 a[i]<0 a[i]<0表示右括号, a [ i ] = 0 a[i]=0 a[i]=0表示这个位置是空的,可以放任何一种左/右括号, ∣ a [ i ] ∣ |a[i]| a[i]表示该位置上的括号是哪一种。
  • 求有多少种合法的括号序列方。 对 1 0 9 + 7 10^9+7 109+7取模。
  • 多组数据 T ( 1 ≤ T ≤ 20 , 1 ≤ n ≤ 500 , 1 ≤ m < 1 0 9 + 7 ) . T(1≤T≤20,1≤n≤500,1≤m<10^9+7). T(1T20,1n500,1m<109+7).

分析
题目已经给出括号所在的位置,可以考虑区间DP

状态定义: f [ i ] [ j ] : i − j f[i][j]:i-j f[i][j]:ij形成的合法括号序列方案数且 a [ i ] 与 a [ j ] a[i]与a[j] a[i]a[j]相匹配
如果只有一个状态表示的话,在状态转移的时候 f [ i ] [ j ] = k ∗ f [ i + 1 ] [ j − 1 ] f[i][j]=k*f[i+1][j-1] f[i][j]=kf[i+1][j1]一定会少算很多情况,且 f [ i ] [ j ] f[i][j] f[i][j]这种状态也无法求出来结果,考虑再定义一个状态:
g [ i ] [ j ] : i − j g[i][j]:i-j g[i][j]:ij形成的合法括号序列方案数

那么 f [ i ] [ j ] f[i][j] f[i][j]就可以通过 s ∗ g [ i + 1 ] [ j − 1 ] s*g[i+1][j-1] sg[i+1][j1]转移过来
g [ i ] [ j ] g[i][j] g[i][j]也可以根据状态 f f f划分出来, g [ i ] [ j ] = ∑ 0 k g [ i ] [ i + k − 1 ] ∗ f [ i + k ] [ j ] g[i][j]=∑^k_0g[i][i+k-1]*f[i+k][j] g[i][j]=0kg[i][i+k1]f[i+k][j]

要注意在转移 f [ i ] [ j ] f[i][j] f[i][j]的时候, a [ l ] = = 0 ∣ ∣ a [ r ] = = 0 a[l]==0||a[r]==0 a[l]==0∣∣a[r]==0不能保证这个状态是合法的,需要加上
( a [ l ] = = 0   a n d   a [ r ] < 0 ) o r ( a [ r ] = = 0   a n d   a [ l ] > 0 ) (a[l]==0~ and ~a[r]<0) or(a[r]==0~ and~ a[l]>0) (a[l]==0 and a[r]<0)or(a[r]==0 and a[l]>0)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
#define x first
#define y second
using namespace std;
typedef long long ll;
const int N=510,mod=1e9+7;
int a[N];
ll g[N][N];//i~j的所有合法方案 +=g[l][k]*f[k+1][r]
ll f[N][N];//i~j的所有合法方案且a[i]与a[j]相匹配 g[i+1][j-1]*s
void solve()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
        for(int j=i;j<=n;j++)
        g[i][j]=f[i][j]=0;
    if(n&1)
    {
        cout<<0<<endl;
        return;
    }
    for(int len=2;len<=n;len+=2)
    {
        for(int l=1;l+len-1<=n;l++)
        {
            int r=l+len-1;
            int s=0;
            if(a[l]==a[r])//两边相等
            {
                if(a[l]==0) s=m;
            }
            else //两边不等
            {
                if((a[l]==0&&a[r]<0) || (a[r]==0&&a[l]>0))
                s=1;
                else if(a[l]+a[r]==0&&a[l]>a[r])
                s=1;
            }
            if(l+1==r) f[l][r]=s;
            else
            f[l][r]=g[l+1][r-1]*s%mod;
            for(int k=0;l+k-1<=r;k+=2)
            {
                if(!k) g[l][r]=f[l][r];
                else g[l][r]=(g[l][r]+1ll*g[l][l+k-1]*f[l+k][r])%mod;
            }
        }
    }
    cout<<g[1][n]<<endl;
}
int main()
{
    int t;
    t=1;
	cin>>t;
	for(int i=1;i<=t;i++)
	{
		solve();
	}
    return 0;
}
/*
1
4 3
1 -1 -1 0
*/

1002 Link with Running (最短路图 缩点 拓扑排序)

链接:1002 Link with Running

题意:

  • 在一个 n n n个点 m m m条有向边的图上,边为一条 u u u v v v 的有向边每个边有两种权值 e e e p p p ,求 1 1 1号点到 n n n号点的路径,路径需要满足 ∑ e ∑e e最小,在 ∑ e ∑e e最小的前提下让 ∑ p ∑p p最大。输出 m i n e 和 m a x p min_e和 max_p minemaxp
  • 题目保证一定可以输出两个数。

数据范围:

  • 多组数据 T ( 1 ≤ T ≤ 12 , 2 ≤ n ≤ 1 0 5 , 1 ≤ m < 3 ∗ 1 0 5 ) . T(1≤T≤12,2≤n≤10^5,1≤m<3*10^5). T(1T12,2n105,1m<3105).
    ( 1 ≤ u i , v i ≤ n , 0 ≤ e i ​ , p i ≤ 109 ) (1≤u _i ,v _i ≤n,0≤e _i​ ,p _i ≤10 9 ) (1ui,vin,0ei,pi109)

分析:

  • 首先可以以e为边权跑一次最短路,求出来 m i n e min_e mine,然后建出来最短路图,考虑尝试一波在最短路图上以p为权值跑最长路的spfa,不出意外果然TLE了。

  • 再考虑拓扑排序求最长路,对于边权都是正数的图,建出来的最短路图是不存在环的,但是可以发现 e e e可能会取到 0 0 0,因此我们建出来的最短路图是存在环的,而且这个环 ( e i , p i ) (ei,pi) (ei,pi)一定是 e i = = 0   p i > = 0 ei==0 ~pi>=0 ei==0 pi>=0的,但是题目又保证了一定会输出两个数,也就是有解,也就是说最短路图上虽然可能会存在 e i = = 0   p i > = 0 ei==0 ~pi>=0 ei==0 pi>=0的环,但是在 1 1 1号点到 n n n号点的路径上,如果有环的话,这个环一定 e i = = 0 p i = = 0 ei==0 pi==0 ei==0pi==0的,否则 m a x p max_p maxp就是 I N F INF INF了。那么就可以直接将 S C C SCC SCC缩点跑拓扑排序即可。

    要注意堆的第一维开成 l o n g   l o n g long~long long long类型。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define x first
#define y second
using namespace std;
typedef long long ll;
const int N=2e5+10,M=5e5+10;
int e[M],ne[M],h[N],idx;
int n,m;
ll dis[N],w[M];
int q[N];
bool st[N];
int dfn[N],low[N],scc_cnt,tms,id[N],top,stk[N];
int cnt[N];//生成拓扑图后的每个分量的出度
bool instk[N],is_edge[M];
struct Edge
{
    int u,v;
    int ee,p;
}edge[M];
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
void dijkstra()
{
    dis[1]=0;
    priority_queue<pair<ll,int>,vector<pair<ll,int>>,greater<pair<ll,int>>> heap;
    heap.push({0,1});
    while(heap.size())
    {
        auto t=heap.top();
        heap.pop();
        int ver=t.y;
        if(st[ver]) continue;
        st[ver]=true;
        for(int i=h[ver];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dis[j]>dis[ver]+w[i])
            {
                dis[j]=dis[ver]+w[i];
                heap.push({dis[j],j});
            }
        }
    }
}
void build()//建最短路图
{
    for(int i=1;i<=n;i++) h[i]=-1,st[i]=false;
    idx=0;
    for(int i=1;i<=m;i++)
    {
        int u=edge[i].u,v=edge[i].v;
        if(dis[u]+edge[i].ee==dis[v])
        {
            is_edge[i]=true;
            add(u,v,edge[i].p);
          //  cout<<u<<" "<<v<<" "<<edge[i].p<<endl;
        }
    }
    for(int i=1;i<=n;i++)
    {
        dis[i]=-1e18;
        dfn[i]=low[i]=0;
    }
    idx=top=scc_cnt=tms=0;
}
void tarjan(int u)
{
    dfn[u]=low[u]=++tms;
    stk[++top]=u;
    instk[u]=true;
    for(int i=h[u];i!=-1;i=ne[i])
    {
        int j=e[i];
        if(!dfn[j])
        {
            tarjan(j);
            low[u]=min(low[u],low[j]);
        }
        else if(instk[j])
        {
            low[u]=min(low[u],low[j]);
        }
    }
    if(dfn[u]==low[u])
    {
        scc_cnt++;
        int y;
        do
        {
            y=stk[top--];
            instk[y]=false;
            id[y]=scc_cnt;
        }while(u!=y);
    }
}
void rebuild()//建拓扑图
{
    for(int i=1;i<=n;i++)
        h[i]=-1,cnt[i]=0;
    for(int i=1;i<=m;i++)
    {
        if(is_edge[i])
        {
            int u=edge[i].u,v=edge[i].v,p=edge[i].p;
            int fu=id[u],fv=id[v];
            if(fu!=fv)
            {
                add(fu,fv,p);
                cnt[fv]++;
               // cout<<u<<"--"<<v<<"--"<<p<<endl;
            }
        }
        is_edge[i]=false;
    }
}
void toposort()
{
    int hh=0,tt=0;
    dis[id[1]]=0;
    for(int i=1;i<=scc_cnt;i++)
    {
        if(!cnt[i])
        {
            q[tt++]=i;
            //dis[i]=0;
        }
    }
    while(hh<tt)
    {
        int t=q[hh++];
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            dis[j]=max(dis[j],dis[t]+w[i]);
            cnt[j]--;
            if(!cnt[j]) q[tt++]=j;
        }
    }
}
void solve()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        id[i]=0;
        h[i]=-1;
        st[i]=instk[i]=false;
        dis[i]=1e18;
    }
    idx=0;
    for(int i=1;i<=m;i++)
    {
        int u,v,ee,p;
        scanf("%d %d %d %d",&u,&v,&ee,&p);
        edge[i]={u,v,ee,p};
        add(u,v,ee);//先以e跑出来最短路图
    }
    dijkstra();
   cout<<dis[n]<<" ";
    build();
    for(int i=1;i<=n;i++)
        if(!dfn[i])
        tarjan(i);
    rebuild();
   // for(int i=1;i<=n;i++)
       // cout<<"点所在的强连通分量及入度--  "<<i<<" "<<id[i]<<" "<<cnt[id[i]]<<endl;
    toposort();
   // for(int i=1;i<=scc_cnt;i++)
    //cout<<dis[i]<<endl;
    cout<<dis[id[n]]<<endl;
}
int main()
{
    int t;
    t=1;
    cin>>t;
    for(int i=1;i<=t;i++)
    {
        solve();
    }
    return 0;
}

1003 Magic (差分约束)

链接:1003 Magic

题意:

  • n n n个排列在一行的魔法塔,可以将 a a a个魔法原料放进第i个魔法塔里,第 i i i个魔法塔就会让 ( i − k , i + k ) (i-k,i+k) ik,i+k内范围的魔法塔增加 a a a点魔力值。
  • 每个魔法塔都需要达到一定的魔力值 p i p_i pi且存在一些限制: [ L , R ] [L,R] [L,R]的魔法塔里魔法原料的和不能超过 B B B
  • 求最少一共需要多少魔法原料才能使得每个魔法塔都至少满足其需要的魔力值。如果无法满足输出 − 1 -1 1

分析:

最主要的限制是来自于 [ L , R ] [L,R] [L,R]的魔法塔里魔法原料的和不能超过 B B B,区间我们没有办法直接限制,因此可以考虑用前缀和来限制区间, d i s [ i ] dis[i] dis[i]表示前i个魔法塔放的魔法原料的总和。求最小值跑最长路,注意建边的方向。

  • 那么区间限制就会转换为 d i s [ R ] − B < = d i s [ L − 1 ] dis[R]-B<=dis[L-1] dis[R]B<=dis[L1],一条 R − − > L − 1 R-->L-1 R>L1,权值为 − B -B B
  • 每个魔法塔都需要满足各自的魔力值,第 i i i个魔法塔的魔力值来源只会来自于 ( i − k + 1 , i + k − 1 ) (i-k+1,i+k-1) (ik+1,i+k1)范围内的魔法塔,转换为 d i s [ i − k ] + p i < = d i s [ i + k − 1 ] dis[i-k]+p_i<=dis[i+k-1] dis[ik]+pi<=dis[i+k1],一条 i − k − − > i + k − 1 i-k-->i+k-1 ik>i+k1,权值为 p i p_i pi
  • 每个魔法塔里的魔法原料最少为 0 0 0,转换为 d i s [ i − 1 ] + 0 < = d i s [ i ] dis[i-1]+0<=dis[i] dis[i1]+0<=dis[i],一条 i − 1 − − > i i-1-->i i1>i的边,权值为 0 0 0

求的是绝对大小,且1号点可以到达所有的边,可以认为 d i s [ 1 ] = d i s [ 0 ] dis[1]=dis[0] dis[1]=dis[0]因此只需要用源点 0 0 0 1 1 1号点之间连一条权值为 0 0 0的边即可。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#define x first
#define y second
using namespace std;
typedef long long ll;
const int N=1e4+10,M=1e5+10,INF=-1e8;
int e[M],ne[M],h[N],idx;
int n,m;
int dis[N],w[M],cnt[N];
int stk[N];
bool st[M];
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
bool spfa()
{
    int top=0;
    dis[0]=0;
    st[0]=true;
    stk[++top]=0;
    cnt[0]++;
    while(top)
    {
        int t=stk[top--];
        st[t]=false;
        for(int i=h[t];i!=-1;i=ne[i])
        {
            int j=e[i];
            if(dis[j]<dis[t]+w[i])
            {
                dis[j]=dis[t]+w[i];
                if(!st[j])
                {
                    st[j]=true;
                    cnt[j]++;
                    stk[++top]=j;
                    if(cnt[j]>n) return false;
                }
            }
        }
    }
    return true;
}
void solve()
{
    int k;
    cin>>n>>k;
    idx=0;
    cnt[0]=0;
    h[0]=-1;
    add(1,0,0);
    for(int i=1;i<=n;i++)
    {
        st[i]=false;
        cnt[i]=0;
        dis[i]=INF;
        h[i]=-1;
        int p;
        scanf("%d",&p);
        add(max(0,i-k),min(n,i+k-1),p);
        add(i-1,i,0);
    }
    int c;
    cin>>c;
    while(c--)
    {
        int l,r,b;
        cin>>l>>r>>b;
        add(r,l-1,-b);
    }
    if(!spfa()) puts("-1");
    else cout<<dis[n]<<endl;
}
int main()
{
    int t;
    t=1;
    cin>>t;
    for(int i=1;i<=t;i++)
    {
        solve();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值