2019 Multi-University Training Contest 1

2019 Multi-University Training Contest 1

目录(当前AC>500的题目,以后慢慢更新)

 HDU - 6578 Blank  (DP+滚动数组)

 HDU - 6579   Operation   (线性基+前缀预处理)

HDU 6581 Vacation  (模拟+二分)

HDU 6582   Path  (最短路+最小割)

HDU - 6586 String  (序列自动机+贪心)


 HDU - 6578 Blank  (DP+滚动数组)

题意:

长度为n的数组要求分别填入{0,1,2,3}四个数中的任意一个,有m个限制条件:区间 [l,r] 中出现的数字种数恰好为x,求方案数

分析:

好奇葩的DP题,看了别人的题解后开明了许多,这种状态转移真的是学到了。

参考博客:https://www.cnblogs.com/zhuyou/p/11273721.html

 

首先, 我们可以用 dp[i][j][k][w]来表示方案数, i, j, k, w不是特指对应某个数字,而是四种不同的数字从小到大最后出现的位置 (i < j < k <w)

那么我们就能推出dp方程:

dp[i][j][k][w] += dp[i][j][k][w-1]

dp[i][j][w-1][w] += dp[i][j][k][w-1]

dp[i][k][w-1][w] += dp[i][j][k][w-1]

dp[j][k][w-1][w] += dp[i][j][k][w-1]

 

我们发现后一位都只与 w-1 有关,100^4的空间太大,所以我们要做一个优化,把最后一维改成大小为2的滚动数组

dp[i][j][k][now] += dp[i][j][k][pre]

dp[i][j][pre][now] += dp[i][j][k][pre]

dp[i][k][pre][now] += dp[i][j][k][pre]

dp[j][k][pre][now] += dp[i][j][k][pre]

now = (pre+ 1)\%2 = pre \oplus1

转移dp的时候考虑一下限制条件就OK啦.

#include<bits/stdc++.h>

#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;

const int inf=0x3f3f3f3f;

const ll mod=998244353;
const int maxn=105;

ll dp[maxn][maxn][maxn][2];
vector<pair<int,int> > vec[maxn];

bool judge(int a,int b,int c,int d){
    for(int i=0;i<vec[d].size();i++){
        int l=vec[d][i].first;
        int x=vec[d][i].second;
        if(x==1&&l<=c) return false;
        if(x==2&&(l<=b||l>c)) return false;
        if(x==3&&(l<=a||l>b)) return false;
        if(x==4&&(l>a)) return false;
    }
    return true;
}

int main(){
    #ifdef io
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif

    int T;
    scanf("%d",&T);
    while(T--){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            vec[i].clear();
        }
        while(m--){
            int l,r,x;
            scanf("%d%d%d",&l,&r,&x);
            vec[r].push_back(make_pair(l,x));
        }
        dp[0][0][0][0]=1;
        int now=1;
        ll ans=0;
        for(int w=1;w<=n;w++){
            for(int k=0;k<=w;k++){
                for(int j=0;j<=k;j++){
                    for(int i=0;i<=j;i++){
                        dp[i][j][k][now]=0;
                    }
                }
            }
            for(int k=0;k<w||(!w&&k<=w);k++){
                for(int j=0;j<k||(!k&&j<=k);j++){
                    for(int i=0;i<j||(!j&&i<=j);i++){
                        if(!judge(i,j,k,w-1)){
                            dp[i][j][k][now^1]=0;
                            continue;
                        }
                        dp[i][j][k][now]=(dp[i][j][k][now]+dp[i][j][k][now^1])%mod;
                        dp[i][j][w-1][now]=(dp[i][j][w-1][now]+dp[i][j][k][now^1])%mod;
                        dp[i][k][w-1][now]=(dp[i][k][w-1][now]+dp[i][j][k][now^1])%mod;
                        dp[j][k][w-1][now]=(dp[j][k][w-1][now]+dp[i][j][k][now^1])%mod;
                    }
                }
            }
            now^=1;
        }
        now^=1;
        for(int k=0;k<n;k++){
            for(int j=0;j<k||(!k&&j<=k);j++){
                for(int i=0;i<j||(!j&&i<=j);i++){
                    if(judge(i,j,k,n))
                        ans=(ans+dp[i][j][k][now])%mod;
                }
            }
        }
        printf("%lld\n",ans);
    }
    return 0;
}

 

 HDU - 6579   Operation   (线性基+前缀预处理)

题意:

计算从给定区间中选取一些数使得异或和最大。

分析:

cf上有一道题跟这个很像,但是那道题可以离线,而这道题必须在线。

但是大致思路差不多,考虑维护每个点的前缀线性基,线性基里将靠右的数字尽可能放高位,就是存一个额外存一个位置 p,表示这个位上的数的位置,从高位到低位扫,如果当前位置大于这个位上的位置那么交换,然后就得到了一个靠右的数字尽可能在高位的线性基然后对于询问 [l,r] 在 r 的前缀线性基里找,只在位置大于等于 l 的位更新答案

#include<bits/stdc++.h>

#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;

const int inf=0x3f3f3f3f;

const int maxn=1e6+10;

struct Liner_Basis{

    int d[32],nd[maxn][32];
    int pos[32],npos[maxn][32];

    void init(){
        mm(d,0);
    }

    void insert(int x,int r){
        int R=r;
        for(int i=31;i>=0;i--){
            if(x&(1<<i)){
                if(!d[i]){
                    d[i]=x;
                    pos[i]=r;
                    break;
                }else if(pos[i]<r){
                    swap(pos[i],r);
                    swap(d[i],x);
                }
                x^=d[i];
            }
        }
        for(int i=31;i>=0;i--){
            nd[R][i]=d[i];
            npos[R][i]=pos[i];
        }
    }

    int get_max(int l,int r){
        int res=0;
        for(int i=31;i>=0;i--){
            if(npos[r][i]>=l){
                res=max(res,res^nd[r][i]);
            }
        }
        return res;
    }
}lb;

int main(){
    #ifdef io
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif

    int T;
    scanf("%d",&T);
    while(T--){
        int n,m;
        scanf("%d%d",&n,&m);
        lb.init();
        for(int i=1;i<=n;i++){
            int a;
            scanf("%d",&a);
            lb.insert(a,i);
        }
        int last=0;
        while(m--){
            int op;
            scanf("%d",&op);
            if(op==0){
                int l,r;
                scanf("%d%d",&l,&r);
                l=(l^last)%n+1;
                r=(r^last)%n+1;
                if(l>r) swap(l,r);
                last=lb.get_max(l,r);
                printf("%d\n",last);
            }else{
                int x;
                scanf("%d",&x);
                x^=last;
                lb.insert(x,++n);
            }
        }
    }
    return 0;
}

HDU 6581 Vacation  (模拟+二分)

模拟每辆车往前跑的过程,对时间进行二分,然后判断是否可行。

#include<bits/stdc++.h>

using namespace std;

const int maxn=1e5+10;
const double eps=1e-10;

double l[maxn],s[maxn],v[maxn],z[maxn];
int n;

bool judge(double t){
    if(v[n]*t<s[n]) return false;
    double up,len;
    for(int i=1;i<=n;i++){
        len=v[i]*t+z[i];
        if(i==1){
            up=len;
            up-=l[i];
            if(up<s[n]) return false;
        }else if(i<n){
            if(len<up) up=len;
            up-=l[i];
            if(up<s[n]) return false;
        }else{
            if(len<up) up=len;
            if(up<s[n]) return false;
        }
    }
    return true;
}

int main(){

    while(scanf("%d",&n)!=EOF){
        n++;
        for(int i=n;i>=1;i--){
            scanf("%lf",&l[i]);
        }
        double lim;
        for(int i=n;i>=1;i--){
            scanf("%lf",&s[i]);
            if(i==n) lim=s[i],z[i]=0;
            else z[i]=lim-s[i];
        }
        for(int i=n;i>=1;i--){
            scanf("%lf",&v[i]);
        }
        double l=0,r=1e10;
        while(l+eps<r){
            double mid=(l+r)/2.0;
            if(judge(mid)) r=mid;
            else l=mid;
        }
        printf("%.10f\n",r);
    }
    return 0;
}

HDU 6582   Path  (最短路+最小割)

题意:

给出有向图,删掉边使得1到n的最短路改变,删掉边的代价为该边的边权。求最小代价。

分析:

要将最短路改变,那么肯定在最短路上进行操作,很容易得到用最短路建图求个最小割就好了。最短路建图就是满足

dis[u]+w==dis[v] 的点。

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

const ll MAXN=1e4+10;
const ll MAXM=2e4+10;
const ll INF=0x3f3f3f3f3f3f3f3f;

struct Edge{
    ll to,next,cap,flow;
}edge[MAXM];
ll tol,head[MAXN];

void init(){
    tol=2;
    memset(head,-1,sizeof(head));
}

void addedge(ll u,ll v,ll w,ll rw){
    edge[tol].to=v;edge[tol].cap=w;edge[tol].flow=0;
    edge[tol].next=head[u];head[u]=tol++;
    edge[tol].to=u;edge[tol].cap=rw;edge[tol].flow=0;
    edge[tol].next=head[v];head[v]=tol++;
}

ll Q[MAXN];
ll dep[MAXN],cur[MAXN],sta[MAXN];

bool bfs(ll s,ll t,ll n){
    ll front=0,tail=0;
    memset(dep,-1,sizeof(dep[0])*(n+1));
    dep[s]=0;
    Q[tail++]=s;
    while(front<tail){
        ll u=Q[front++];
        for(ll i=head[u];i!=-1;i=edge[i].next){
            ll v=edge[i].to;
            if(edge[i].cap>edge[i].flow&&dep[v]==-1){
                dep[v]=dep[u]+1;
                if(v==t){
                    return true;
                }
                Q[tail++]=v;
            }
        }
    }
    return false;
}

ll dinic(ll s,ll t,ll n){
    ll maxflow=0;
    while(bfs(s,t,n)){
        for(ll i=0;i<n;i++){
            cur[i]=head[i];
        }
        ll u=s,tail=0;
        while(cur[s]!=-1){
            if(u==t){
                ll tp=INF;
                for(ll i=tail-1;i>=0;i--){
                    tp=min(tp,edge[sta[i]].cap-edge[sta[i]].flow);
                }
                maxflow+=tp;
                for(ll i=tail-1;i>=0;i--){
                    edge[sta[i]].flow+=tp;
                    edge[sta[i]^1].flow-=tp;
                    if(edge[sta[i]].cap-edge[sta[i]].flow==0){
                        tail=i;
                    }
                }
                u=edge[sta[tail]^1].to;
            }else if(cur[u]!=-1&&edge[cur[u]].cap>edge[cur[u]].flow&&dep[u]+1==dep[edge[cur[u]].to]){
                sta[tail++]=cur[u];
                u=edge[cur[u]].to;
            }else{
                while(u!=s&&cur[u]==-1){
                    u=edge[sta[--tail]^1].to;
                }
                cur[u]=edge[cur[u]].next;
            }
        }
    }
    return maxflow;
}

struct node{
    ll v;
    ll cost;
    node(ll v,ll cost):v(v),cost(cost){}
};

vector<node> g[MAXN];

void addedge(ll u,ll v,ll w){
    g[u].push_back(node(v,w));
}

bool vis[MAXN];
ll dis[MAXN];

void spfa(ll start){
    memset(vis,0,sizeof vis);
    memset(dis,INF,sizeof dis);
    vis[start]=1;
    dis[start]=0;
    queue<ll> q;
    q.push(start);
    while(!q.empty()){
        ll u=q.front();
        q.pop();
        vis[u]=false;
        for(ll i=0;i<g[u].size();i++){
            ll v=g[u][i].v;
            ll w=g[u][i].cost;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                if(!vis[v]){
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
}

int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        int n,m;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){
            g[i].clear();
        }
        init();
        ll u,v,w;
        while(m--){
            scanf("%lld%lld%lld",&u,&v,&w);
            addedge(u,v,w);
        }
        spfa(1);
        for(int i=1;i<=n;i++){
            u=i;
            for(int j=0;j<g[i].size();j++){
                v=g[i][j].v;
                w=g[i][j].cost;
                if(dis[u]+w==dis[v]){
                    addedge(u,v,w,0);
                }
            }

        }
        printf("%lld\n",dinic(1,n,n));
    }
    return 0;
}

 HDU - 6586 String  (序列自动机+贪心)

题意:

多组输入,每组第一行输入一个字符串s和一个数字k,接下来每行输入26行(l,r),每行两个数字代表在长度为k的子序列中,a~z在这个范围中出现。

分析:

先用序列自动机构造出某个位置后每个字母的个数,每个字母 的第一个位置。
然后每次贪心地加入最小的字符,加入的条件为当前字母加入后,后面的字符满足剩余的条件。

即剩余的字母Ai在不超Ri的情况下能构成k长度的串,剩余的字母Ai+已拿取字母Ai>=Li且满足Li所需的长度小于剩余可添加长度。

#include<bits/stdc++.h>

#define mm(a,b) memset(a,b,sizeof(a))
#define ACCELERATE (ios::sync_with_stdio(false),cin.tie(0))
typedef long long ll;
typedef long double ld;
typedef unsigned long long ull;
#define PI acos(-1.0)
#define E exp(1.0)
//#define io
using namespace std;

const int inf=0x3f3f3f3f;

const int maxn=1e5+10;

int l[30],r[30];
char s[maxn],ans[maxn];
int k;
int nxt[maxn][30],cnt[maxn][30],num[30];

bool judge(int pos,int tot){
    int len=0,dis=0;
    for(int i=0;i<26;i++){
        if(num[i]+cnt[pos][i]<l[i]) return false;
        len+=num[i]+min(cnt[pos][i],r[i]-num[i]);
        dis+=max(0,l[i]-num[i]);
    }
    if(len<k) return false;
    if(dis>k-tot) return false;
    return true;
}

int main(){
    #ifdef io
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    #endif

    while(scanf("%s%d",s+1,&k)!=EOF){
        int len=strlen(s+1);
        for(int i=0;i<26;i++){
            scanf("%d%d",&l[i],&r[i]);
        }
        mm(nxt,-1);
        mm(cnt,0);
        for(int i=len-1;i>=0;i--){
            for(int j=0;j<26;j++){
                nxt[i][j]=nxt[i+1][j];
                cnt[i][j]=cnt[i+1][j];
            }
            nxt[i][s[i+1]-'a']=i+1;
            cnt[i][s[i+1]-'a']++;
        }
        int pos=0,tot=0;
        bool ok=true;
        mm(num,0);
        while(pos<=len&&tot<k){
            bool can=false;
            for(int i=0;i<26;i++){
                if(nxt[pos][i]!=-1&&num[i]<r[i]){
                    num[i]++;
                    if(judge(nxt[pos][i],tot+1)){
                        can=true;
                        ans[tot++]=i+'a';
                        pos=nxt[pos][i];
                        break;
                    }
                    num[i]--;
                }
            }
            if(!can){
                ok=false;
                break;
            }
        }
        ans[tot]='\0';
        if(ok) printf("%s\n",ans);
        else printf("-1\n");
    }
    return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值