2019 Multi-University Training Contest 1

9 篇文章 0 订阅

String

theme:给定一个仅包含小写字母的字符串,求字典序最小的长度为k的子序列满足26个约束条件:第i个约束条件表示字母i出现的次数在[l,r]范围内。|S|≤10^5

solution:最终是要求字典序最大的序列,所以贪心选择,每选一个元素都从a~z顺序选择。所以首先用queue(因为是从前往后弹出,即先进先出)记录下每一个每个字母在数组中的下标,对每一个字母所在的所有位置(但要在上一个被选位置下标之后)检查选了它之后其后元素能否满足条件,检查时检查两方面:设还要选res=k-nowCnt个 1、选res能否把所有还没有L[i]个的字母填满

2、把所有元素都填到R[i]个了是否res为0.

#include<bits/stdc++.h>
using namespace std;
#define far(i,t,n) for(int i=t;i<n;++i)
typedef long long ll;
typedef unsigned long long ull;
using namespace std;

char c[100010];//原数组
char ans[100010];//答案字符串
int k;
int L[26],R[26];
int cnt[100010][30],used[30];//cnt记录当前下标及其后的字符串中各种字母的个数,used记录每个字母已经选了几个
queue<int>q[26];
bool check(int nowCnt,int idx)//判定第nowCnt个字符选中后,其后的序列能否满足要求
{
    int res=k-nowCnt;
    int suml=0;

    far(i,0,26)//判断其后序列能否填到每个L[i]
    {
        if(L[i]-used[i]>cnt[idx][i])
            return false;
        if(L[i]-used[i]>0)
            suml+=L[i]-used[i];
    }

    if(suml>res)
        return false;
    int sumr=0;
    far(i,0,26)//判定要选k个元素会不会有的字母超过R[i]个
        sumr+=min(R[i]-used[i],cnt[idx][i]);
    if(sumr<res)
        return false;
    return true;
}

int main()
{
    while(~scanf("%s%d",c+1,&k))
    {
        far(i,0,26)
            while(!q[i].empty())
                q[i].pop();
        memset(cnt,0,sizeof(cnt));
        memset(used,0,sizeof(used));
        ans[1]=0;
        far(i,0,26)
            scanf("%d%d",&L[i],&R[i]);
        int l=strlen(c+1);
        far(i,1,l+1)
            q[c[i]-'a'].push(i);
        for(int i=l;i>=1;--i)
            far(j,0,26)
                cnt[i][j]=cnt[i+1][j]+((c[i]-'a')==j);

        int nowCnt=0,lastidx=0;//nowCnt记录已选的个数,lastidx记录上一个被选元素的下标
        int flag=0,success=1;//flag标识第nowCnt个元素能否选出
        while(nowCnt<k)
        {
            nowCnt++;
            flag=0;
            far(i,0,26)
            {
                while(q[i].size()&&q[i].front()<=lastidx)
                    q[i].pop();
                if(used[i]<R[i]&&q[i].size())
                {
                    used[i]++;
                    if(check(nowCnt,q[i].front()+1))
                    {
                        lastidx=q[i].front();
                        ans[nowCnt]=c[q[i].front()];
                        ans[nowCnt+1]=0;
                        q[i].pop();
                        flag=1;
                        break;
                    }
                    used[i]--;
                }
             }
             if(!flag)
             {
                 success=0;
                 break;
             }
        }
        ans[k + 1] = '\0';
        if(success)
            printf("%s\n",ans+1);
        else
            puts("-1");
    }
}

Blank 

theme:给定一个大小为n的数组,用0,1,2,3来填充数组,给定m个约束条件,其中第i个条件l,r,x表示数组的区间[l,r]之间不同元素个数为x,求有多少种填充方案?1≤n≤100,1≤m≤100,mod 998244353

Operation

theme:给定长度为n的数组,有m次操作,操作类型有两种:

0 l r:表示询问从区间[l,r]中任意选出若干元素能得到的最大抑或和值

1 x:表示在数组最后插入一个元素x.

1≤n≤5×10^5,1≤m≤5×10^5

solution:线性基。不过因为查询的是[l,r]所以要记录一下每个被插入线性基中元素的下标。

#include<bits/stdc++.h>
#define far(i,s,n) for(int i=s;i<n;++i)
typedef long long ll;
using namespace std;

int b[1000010][35],pos[1000010][35];

void Insert(int x,int i)
{
    int now=i;
    for(int j=30;j>=0;--j)
    {
        b[i][j]=b[i-1][j];
        pos[i][j]=pos[i-1][j];
    }
    for(int j=30;j>=0;--j)
    {
        if(x>>j)
        {
            if(!b[i][j])
            {
                b[i][j]=x;
                pos[i][j]=now;
                return;
            }
            else
            {
                if(now>pos[i][j])
                {
                    swap(pos[i][j],now);
                    swap(x,b[i][j]);
                }
                x^=b[i][j];
            }
        }
    }
}

int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        int n,m;
        cin>>n>>m;
        far(i,1,n+1)
        {
            int a;
            scanf("%d",&a);
            Insert(a,i);
        }
        int pre=0;
        far(i,0,m)
        {
            int opt;
            scanf("%d",&opt);
            if(opt==0)
            {
                int l,r;
                scanf("%d%d",&l,&r);
                l=(l^pre)%n+1,r=(r^pre)%n+1;
                if(l>r)
                    swap(l,r);
                int ans=0;
                for(int j=30;j>=0;--j)
                    if(pos[r][j]>=l)
                        ans=max(ans,ans^b[r][j]);
                pre=ans;
                printf("%d\n",ans);
            }
            else
            {
                int x;
                scanf("%d",&x);
                x=x^pre;
                ++n;
                Insert(x,n);
            }
        }
        for (int i=1;i<=n;++i)
            for (int j=30;j>=0;--j)
                b[i][j]=pos[i][j]=0;
    }
}

Vacation

theme:给定n辆车头离停车线的距离s,车长l,最大速度v,按s从大到小给,要求不能超车行驶(最多车头顶着车尾行驶),问第一辆车(距离最远的那辆)抵达停车线花费的最少时间?1≤n≤10^5

solution:两种考虑方式:1、对于处在最后的车,如果前面有车速度比它小,且比它们中间的车速度都小,则很有可能在它抵达停车线前被堵住了,这时它们都得按堵住的最前面的车的速度行驶,(注意即使这辆车通过了停车线,后面的车还是不能超过他),所以最后一辆车通过停车线的时间相当于堵住它的车x到达停车线的时间+最后一辆车以x的速度行驶它与x间所有车长的时间。所以我们可以枚举每一辆车为堵住最后一辆车的,算出它行驶到停车线的时间+以它的速度行驶它与最后一辆车之间车的车长之和的时间,取最大值即为答案。

#include<bits/stdc++.h>
#define far(i,s,n) for(int i=s;i<n;++i)
typedef long long ll;
using namespace std;

ll s[100010],l[100010],v[100010];
ll suml[100010];

int main()
{
    int n;
    while(~scanf("%d",&n))
    {
        far(i,0,n+1)
            scanf("%lld",&l[i]);
        far(i,0,n+1)
            scanf("%lld",&s[i]);
        far(i,0,n+1)
            scanf("%lld",&v[i]);
        suml[0]=0;
        far(i,1,n+1)
            suml[i]=suml[i-1]+l[i];
        double ans=0;
        far(i,0,n+1)
            ans=max(ans,1.0*(suml[i]+s[i])/v[i]);
        printf("%f\n",ans);
    }
}

Path

theme:给定一个有向图,可以有重边,每条边上有一个权值表示删掉这条边的代价,问最少花费多少代价能使从s到t节点的最短路径增大?1≤n,m≤10000

solution:先找出从s到t的所有最短路径所经过的边,先跑一遍最短路,如果一条边的起始与终止节点u与v满足d[u]+w=d[v],说明这条边在某条最短路上(重边也一样)。得到这些边形成的新图,则问题可以转化为在新图上求最小割,最大流=最小割,跑一遍dinic.

dijkstra时间复杂度为o(VE)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const int maxn = 10010;
const int maxm = 20020;
ll dist1[maxn];
bool vis[maxn];
int n, m, N;

struct Node
{
    int v;
    ll c;
    Node(int _v, ll _c): v(_v), c(_c) {}
    bool operator < (const Node & r) const
    {
        return c > r.c;
    }
};

struct Edge//最短路
{
    int to;
    ll cost;
    Edge(int _to, ll _cost): to(_to), cost(_cost) {}
};

vector <Edge> edge[maxn];

void addedge(int u, int v, ll w) // 最短路加边
{
    edge[u].push_back(Edge(v, w));
}

void dijkstra(int s, ll dist[]) // 最短路
{
    for (int i = 1; i <= N; ++i)
    {
        dist[i] = inf;
        vis[i] = false;
    }
    dist[s] = 0;
    priority_queue <Node> Q;
    Q.push(Node(s, dist[s]));
    while (!Q.empty())
    {
        Node tmp = Q.top();
        Q.pop();
        int u = tmp.v;
        if (vis[u])
            continue;
        vis[u] = true;
        for (int i = 0; i < edge[u].size(); ++i)
        {
            int v = edge[u][i].to;
            ll cost = edge[u][i].cost;
            if (!vis[v] && dist[v] > dist[u] + cost)
            {
                dist[v] = dist[u] + cost;
                Q.push(Node(v, dist[v]));
            }
        }
    }
}

struct Edges
{
    int u, v;
    ll cap;
    Edges() {}
    Edges(int u, int v,ll cap): u(u), v(v), cap(cap) {}
} es[maxm];
int R, S, T;//边下标、源点、汇点
vector <int> tab[maxn]; // 边集
ll dis[maxn];
int current[maxn];

void addedges(int u, int v, ll cap) // 最大流加边
{
    tab[u].push_back(R);
    es[R++] = Edges(u, v, cap); // 正向边
    tab[v].push_back(R);
    es[R++] = Edges(v, u, 0); // 反向边容量为0
    // 正向边下标通过异或就得到反向边下标, 2 ^ 1 == 3 ; 3 ^ 1 == 2
}

int BFS()
{
    queue <int> q;
    q.push(S);
    memset(dis, 0x3f3f, sizeof(dis));
    dis[S] = 0;
    while (!q.empty())
    {
        int h = q.front();
        q.pop();
        for (int i = 0; i < tab[h].size(); i++)
        {
            Edges &e = es[tab[h][i]];
            if (e.cap > 0 && dis[e.v] == inf)
            {
                dis[e.v] = dis[h] + 1;
                q.push(e.v);
            }
        }
    }
    return dis[T] < inf; // 返回是否能够到达汇点
}

ll dinic(int x, ll maxflow)
{
    if (x == T)
        return maxflow;
    // i = current[x] 当前弧优化
    for (int i = current[x]; i < tab[x].size(); i++)
    {
        current[x] = i;
        Edges &e = es[tab[x][i]];
        if (dis[e.v] == dis[x] + 1 && e.cap > 0)
        {
            ll flow = dinic(e.v, min(maxflow, e.cap));
            if (flow)
            {
                e.cap -= flow; // 正向边流量降低
                es[tab[x][i] ^ 1].cap += flow; // 反向边流量增加
                return flow;
            }
        }
    }
    return 0; // 找不到增广路 退出
}

ll DINIC()
{
    ll ans = 0;
    while (BFS()) // 建立分层图
    {
        ll flow;
        memset(current, 0, sizeof(current)); // BFS后应当清空当前弧数组
        while (flow = dinic(S, inf)) // 一次BFS可以进行多次增广
            ans += flow;
    }
    return ans;
}

int a[maxm], b[maxm];
ll c[maxm];

int main()
{
    int kase;
    scanf("%d", &kase);
    while (kase--)
    {
        scanf("%d%d", &n, &m);
        N = n;
        for (int i = 0; i <= N; ++i) // 清空
            edge[i].clear();
        for (int i = 1; i <= m; ++i) // 先正向加边
        {
            scanf("%d%d%lld", &a[i], &b[i], &c[i]);
            addedge(a[i], b[i], c[i]);
        }
        S=1,T=n;
        dijkstra(S, dist1); // 跑S到所有点的最短路

        R = 0;
        for (int i = 0; i <= N; i++)
            tab[i].clear();
        for (int i = 1; i <= m; ++i)//建新图用于最大流
        {
            if (a[i] != b[i] && dist1[a[i]] + c[i]  == dist1[b[i]]) // 最短路上的边
                addedges(a[i], b[i], c[i]);
        }
        ll ans = DINIC(); // 求最大流
        printf("%lld\n", ans);
    }
    return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值