2019 Multi-University Training Contest 1


ABCDEFGHIJKLM
●○

( √:做出; ●:尝试未做出; ○:已补题 )




A Blank

题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6578

题意:n块连续的板,每块板可以涂色 c ∈ { 0 , 1 , 2 , 3 } c\in \{0,1,2,3\} c{0,1,2,3},且有 m 个限制,每个限制 ( l i , r i , x i ) (l_i,r_i,x_i) (li,ri,xi) 要求 [ l i , r i ] [l_i,r_i] [li,ri] 中的所有板恰好有 x i x_i xi 种颜色。问总共有多少种涂色方案。

思路:dp。设 f[i][j][k][q] 表示四种颜色从小到大最后出现的位置为 i、j、k、q 时的涂色方案,显然计算这个时,当前涂色涂到第 q 块板。往后推比较容易想:
f [ i ] [ j ] [ k ] [ q + 1 ] + = f [ i ] [ j ] [ k ] [ q ] f [ j ] [ k ] [ q ] [ q + 1 ] + = f [ i ] [ j ] [ k ] [ q ] f [ i ] [ k ] [ q ] [ q + 1 ] + = f [ i ] [ j ] [ k ] [ q ] f [ i ] [ j ] [ q ] [ q + 1 ] + = f [ i ] [ j ] [ k ] [ q ] f[i][j][k][q+1]+=f[i][j][k][q] \\f[j][k][q][q+1]+=f[i][j][k][q]\\f[i][k][q][q+1]+=f[i][j][k][q]\\f[i][j][q][q+1]+=f[i][j][k][q]\\ f[i][j][k][q+1]+=f[i][j][k][q]f[j][k][q][q+1]+=f[i][j][k][q]f[i][k][q][q+1]+=f[i][j][k][q]f[i][j][q][q+1]+=f[i][j][k][q]
最后一维只有相邻两两相关,可以滚动节省空间;然后处理第 q 块板时,找出 r i = q r_i=q ri=q 的限制,看 最后出现的位置在限制范围内的数目是否等于限制个数 来决定取值是否不为 0 。

不得不说对于这种需要天马行空想象力来dp的题目,经验还是不够。其实这道题数据范围很小,而且是一个计数问题,dp应该是惯常思路,以后要注意。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=102,M=998244353;
int T,n,m,f[maxn][maxn][maxn][2];

int main()
{
    //freopen("input.txt","r",stdin);
    T=read();
    while(T--)
    {
        n=read(),m=read();
        REP(i,0,n) REP(j,i,n) REP(k,j,n) mem(f[i][j][k],0);
        vector<pair<int,int>> L[n+2];
        while(m--)
        {
            int l=read(),r=read(),x=read();
            L[r].push_back(make_pair(l,x));
        }

        f[0][0][0][0]=1;
        int cur=0;
        REP(l,1,n)
        {
            cur^=1;
            REP(i,0,l) REP(j,i,l) REP(k,j,l) f[i][j][k][cur]=0;
            REP(i,0,l) REP(j,i,l) REP(k,j,l)
            {
                f[i][j][k][cur]=(f[i][j][k][cur]+f[i][j][k][cur^1])%M;
                f[j][k][l-1][cur]=(f[j][k][l-1][cur]+f[i][j][k][cur^1])%M;
                f[i][k][l-1][cur]=(f[i][k][l-1][cur]+f[i][j][k][cur^1])%M;
                f[i][j][l-1][cur]=(f[i][j][l-1][cur]+f[i][j][k][cur^1])%M;
                REP(v,0,L[l].size()-1)
                {
                    int a=L[l][v].first,x=L[l][v].second;
                    #define can(i,j,k,q) ((i>=a)+(j>=a)+(k>=a)+(q>=a)==x)
                    if(!can(i,j,k,l)) f[i][j][k][cur]=0;
                    if(!can(j,k,l-1,l)) f[j][k][l-1][cur]=0;
                    if(!can(i,k,l-1,l)) f[i][k][l-1][cur]=0;
                    if(!can(i,j,l-1,l)) f[i][j][l-1][cur]=0;
                }
            }
        }

        int ans=0;
        REP(i,0,n) REP(j,i,n) REP(k,j,n)
            ans=(ans+f[i][j][k][cur])%M;
        cout<<ans<<endl;
    }

    return 0;
}



B Operation

题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6579

题意:有一个整数数列,两种操作:(1) 给定l, r,从 [l, r] 中选若干个数使得其异或和最大,打印最大异或和;(2) 在数列尾部添加一个数。

思路:很明显的线性基的题目。一开始我的思路是用线段树维护区间线性基,不过这样由于线段树每个结点都是几十个int,会MLE,所以不行。考虑线性基+前缀和,每一个位置记录的线性基是前面所有数的线性基,当然我们要保证线性基的选取尽可能接近这个数,也就是说,我们要记录每个位的基的选取位置,然后插入的时候尽可能使这个位置靠右,这样当选取 [l, r] 中的线性基求最大异或和时,就可以等价于选取 r 的线性基前缀和,然后判断每个位的基的选取位置是否大于等于 l 来决定这个位是否有效。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

struct linear_basis
{
    static const int N=29;
    int b[N+1],t[N+1];
    linear_basis() {mem(b,0),mem(t,0);}

    void insert(int x,int id)
    {
        for(int i=N;i>=0;i--)
            if(x&(1<<i))
            {
                if(!b[i]) {b[i]=x; t[i]=id; break;}
                else if(id>t[i])
                {
                    swap(b[i],x);
                    swap(t[i],id);	// 这里用替换是因为可能这个被替换的基还能比之前的更优
                }
                x^=b[i];
            }
    }
    int max_sum(int l)
    {
        int ans=0;
        for(int i=N;i>=0;i--)
            if(t[i]>=l && (ans^b[i])>ans) ans^=b[i];
        return ans;
    }
};

const int maxn=1e6+5;
linear_basis lb[maxn];

int main()
{
    //freopen("input.txt","r",stdin);
    int n,m,T=read();
    while(T--)
    {
        n=read(),m=read();
        REP(i,1,n)
        {
            lb[i]=lb[i-1];
            int a=read();
            lb[i].insert(a,i);
        }
        int ans=0;
        while(m--)
        {
            int op=read();
            if(op==1)
            {
                int x=read()^ans;
                n++;
                lb[n]=lb[n-1];
                lb[n].insert(x,n);
            }
            else
            {
                int l=(read()^ans)%n+1;
                int r=(read()^ans)%n+1;
                if(l>r) swap(l,r);
                ans=lb[r].max_sum(l);
                printf("%d\n",ans);
            }
        }
        //printf("%d\n",st.n);
    }

    return 0;
}



C

题目地址:

题意

思路

代码




D Vacation

题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6581

题意:马路上有n辆车,每辆车定义了长度、速度和车头距终点的距离。不能超车,并且当车经过终点仍然保持原来的速度继续前进,问最后(最远)那辆车要经过多久可以到达终点。

思路:直接计算时间似乎比较复杂,不过由于答案具有单调性,故可以二分。对于某个固定时间t,可以算出第一辆车(最前面的车)到达的位置,然后可以算出第二辆车到达的位置(然后根据前面的来取值),…,这样就可以算出最后一辆车到达的位置来判断这个时间是否可行。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e5+5;
double l[maxn],s[maxn],v[maxn],temp[maxn];
int n;

bool can(double t)
{
    temp[n]=s[n]-v[n]*t;
    REP_(i,n-1,0)
    {
        double q=s[i]-v[i]*t;
        if(q<temp[i+1]+l[i+1]) q=temp[i+1]+l[i+1];
        temp[i]=q;
    }
    return temp[0]<=0;
}

int main()
{
    //freopen("input.txt","r",stdin);
    while(~scanf("%d",&n))
    {
        REP(i,0,n) l[i]=read();
        REP(i,0,n) s[i]=read();
        REP(i,0,n) v[i]=read();
        double ll=0,rr=1e9+5,mid;
        while(rr-ll>1e-9)
        {
            mid=(ll+rr)/2;
            if(can(mid)) rr=mid;
            else ll=mid;
        }
        printf("%.9f\n",rr);
    }

    return 0;
}



E Path

题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6582

题意:有一张有向图,我们可以删边,问至少需要删去总长度为多少的边时,可以使得从1-n的最短路的长度变长。

思路:我们首先找出哪些边属于“最短路边”(如果一条边属于“最短路边”,那么一定存在一条最短路包含这条边),找的方法很简单:首先求一次 1 结点的单源最短路 d1[],然后求一次反向建图的 n 结点的单源最短路 d2[](其实就是原图中各个点到 n 的最短路),那么一条边 <u, v, w> 属于“最短路边”,当且仅当 d1[u]+w+d2[v]=d1[n]。

把所有“最短路边”拿来建一张图,我们的目的就是删去尽可能权值和更小的边集,使得 1 和 n 不连通(那么原图中就不存在原来的最短路了),这就是一个最小割的问题。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e4+5;
const LL inf=1e16;
struct edge {int to; LL cap; int rev,is_rev;};
vector<edge> G[maxn];
LL dis[maxn];
int book[maxn];

struct edge_ {int v; LL w;};
struct _edge {int u,v; LL w;} e[maxn];
vector<edge_> GG[maxn];
int n,m,vis[maxn];
LL d[2][maxn];

void init()
{
    REP(i,1,n) GG[i].clear(),G[i].clear();
}

void dijkstra(int s,LL *d)
{
    fill(d,d+n+5,inf);
    mem(vis,0);
    d[s]=0;
    typedef pair<LL,int> P;
    priority_queue<P,vector<P>,greater<P> > Q;
    Q.push(P(0,s));
    while(!Q.empty())
    {
        P p=Q.top(); Q.pop();
        LL dis=p.first;
        int u=p.second;
        if(vis[u]) continue;
        d[u]=dis; vis[u]=1;
        REP(i,0,GG[u].size()-1)
        {
            int v=GG[u][i].v;
            LL w=GG[u][i].w;
            if(vis[v] || dis+w>d[v]) continue;
            Q.push(P(d[v]=dis+w,v));
        }
    }
}

void add_edge(int from,int to,LL cap)
{
    G[from].push_back((edge){to,cap,(int)G[to].size(),0});
    G[to].push_back((edge){from,0,(int)G[from].size()-1,1});
}

void BFS(int s)
{
    mem(dis,-1); dis[s]=0;
    queue<int> que; que.push(s);
    while(!que.empty())
    {
        int v=que.front();que.pop();
        REP(i,0,G[v].size()-1)
        {
            edge e=G[v][i];
            if(dis[e.to]<0 && e.cap) dis[e.to]=dis[v]+1,que.push(e.to);
        }
    }
}

LL dfs(int s,int t,LL flow)
{
    if(s==t) return flow;
    for(int &i=book[s];i<(int)G[s].size();i++)
    {
        edge &e=G[s][i];
        if(e.cap && dis[s]<dis[e.to])
        {
            LL flow2=dfs(e.to,t,min(flow,e.cap));
            if(!flow2) continue;
            e.cap-=flow2;
            G[e.to][e.rev].cap+=flow2;
            return flow2;
        }
    }
    return 0;
}

LL max_flow(int s,int t)
{
    LL flow=0,flow2;
    while(1)
    {
        BFS(s);
        if(dis[t]<0) return flow;
        mem(book,0);
        while((flow2=dfs(s,t,inf))>0) flow+=flow2;
    }
}

int main()
{
    //freopen("input.txt","r",stdin);
    int T=read();
    while(T--)
    {
        n=read(),m=read();
        init();
        REP(i,1,m)
        {
            int u=read(),v=read(); LL w=read();
            GG[u].push_back((edge_){v,w});
            e[i]=(_edge){u,v,w};
        }
        dijkstra(1,d[0]);
        init();
        REP(i,1,m) GG[e[i].v].push_back((edge_){e[i].u,e[i].w});
        dijkstra(n,d[1]);
        REP(i,1,m)
        {
            int u=e[i].u,v=e[i].v;
            LL w=e[i].w;

            if(d[0][u]+w+d[1][v]==d[0][n])
                add_edge(u,v,w);
        }
        printf("%lld\n",max_flow(1,n));
    }

    return 0;
}



F Typewriter

题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6583

题意:有一个打字机,每次可以花费 p 去打印任意一个字符,或者花费 q 去打印之前打印过的一段字符串。给出一个字符串问打印该字符串的最少花费是多少。

思路:dp+SAM。dp的思路应该比较好想,设 f[i] 为打印到第 i 个字符的最少花费,那么必然有
f [ i ] = m i n { f [ i − 1 ] + p ,   f [ j ] + q } f[i]=min\{f[i-1]+p, \ f[j]+q\} f[i]=min{f[i1]+p, f[j]+q}
其中,j 是最小的下标,使得子串 s[j+1,…,i] 是 s[1,…,j] 的子串。问题就在于如何寻找这个最小的 j。可以发现,如果 i 越大,那么必然 j 也会越大,因为如果 j 反而更小,那么之前更小的 i 就肯定可以用这个更小的 j(更短的子串)。所以就可以维护一个 j 以及子串 s[1,…,j] 的前缀信息(SAM),并且保证 s[j+1,…,i] 是可以在 SAM 中匹配。对于一个新的 s[i+1],如果可以匹配自然 j 不变,否则,循环判断处理将 s[++j] 放进SAM(此时的 s[j+1,…,i] 仍然是子串),并且及时将当前匹配位点视情况跳link(因为 j++了,所以当前的匹配字符串长度变小了,就有可能变成了当前匹配位点的后缀连接的子串集中的某一个)即可。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

// 1号是S结点
struct suffix_automaton
{
    #define maxn ((int)(2e5+5))
    int maxlen[maxn<<1],trans[maxn<<1][26],link[maxn<<1],tot=1,last=1;

    void init()
    {
        REP(i,1,tot) maxlen[i]=link[i]=0,mem(trans[i],0);
        tot=last=1;
    }

    void extend(int c)
    {
        int cur=++tot,p;
        maxlen[cur]=maxlen[last]+1;
        for(p=last;p && !trans[p][c];p=link[p]) trans[p][c]=cur;
        if(!p) link[cur]=1;
        else
        {
            int q=trans[p][c];
            if(maxlen[q]==maxlen[p]+1) link[cur]=q;
            else
            {
                int clone=++tot;
                maxlen[clone]=maxlen[p]+1;
                memcpy(trans[clone],trans[q],sizeof(trans[q]));
                for(;p && trans[p][c]==q;p=link[p]) trans[p][c]=clone;
                link[clone]=link[q];
                link[q]=link[cur]=clone;
            }
        }
        last=cur;
    }
};

char s[maxn];
suffix_automaton sam;
LL f[maxn],p,q;

int main()
{
    //freopen("input.txt","r",stdin);
    while(~scanf("%s",s+1))
    {
        int n=strlen(s+1);
        p=read(),q=read();
        sam.init();

        int j=0,now=1;
        f[1]=p;
        sam.extend(s[++j]-'a');

        REP(i,2,n)
        {
            f[i]=f[i-1]+p;
            while(now>1 && i-j-1<=sam.maxlen[sam.link[now]]) now=sam.link[now];
            while(!sam.trans[now][s[i]-'a'])
            {
                sam.extend(s[++j]-'a');
                while(now>1 && i-j-1<=sam.maxlen[sam.link[now]]) now=sam.link[now];
            }
            while(now>1 && i-j-1<=sam.maxlen[sam.link[now]]) now=sam.link[now];
            now=sam.trans[now][s[i]-'a'];
            f[i]=min(f[i],f[j]+q);
            //cout<<i<<' '<<j<<endl;
        }
        printf("%lld\n",f[n]);
    }

    return 0;
}



G

题目地址:

题意

思路

代码




H

题目地址:

题意

思路

代码




I String

题目地址: http://acm.hdu.edu.cn/showproblem.php?pid=6586

题意:给出一个仅包含小写字母的字符串,要求选出长度为k的子序列(不要求连续),使得字典序最小,并且每个字母出现次数在 L[c] 和 R[c] 之间。

思路:其实就是一个贪心的思路,组队模拟赛的时候本来已经有思路了,但不知道为什么写着写着思路就混乱,写成了“贪心地按字母顺序放入”。正解应该是“从前到后对于每一位,贪心地选取满足条件的最小的字母”,这里满足条件主要是指如果当前位置填了这个字母,那么要保证后面有足够的字母能够满足限制,以及剩余位置足够满足限制等等,满足条件还是要细心想一想。

可以给每个字母建一个队列,并且记录上一个选择字母的坐标,然后贪心即可。

代码

#pragma GCC optimize(2)
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=1e5+5;
int k,n,L[maxn],R[maxn],num[200],f[127][maxn],ans[maxn];
char s[maxn];
queue<int> que[127];

bool can(int w,int tot)
{
    if(tot+n-w<k) return 0;
    REP(i,'a','z') if(num[i]+f[i][w+1]<L[i]) return 0;
    int sum=0;
    REP(i,'a','z') if(L[i]>num[i]) sum+=L[i]-num[i];
    if(k-tot<sum) return 0;
    return 1;
}

int main()
{
    //freopen("input.txt","r",stdin);
    while(~scanf("%s",s+1))
    {
        n=strlen(s+1); k=read(); mem(num,0);
        REP(i,'a','z') L[i]=read(),R[i]=read(),f[i][n+1]=0;
        REP(i,'a','z') while(!que[i].empty()) que[i].pop();
        REP_(i,n,1) REP(j,'a','z')
            f[j][i]=(j==s[i])?f[j][i+1]+1:f[j][i+1];
        REP(i,1,n) que[s[i]].push(i);

        int r=0,tot=0;
        REP(i,1,k) REP(c,'a','z')
        {
            while(!que[c].empty() && que[c].front()<=r) que[c].pop();
            if(que[c].empty() || num[c]>=R[c]) continue;
            num[c]++;
            if(can(que[c].front(),tot+1)) {r=que[c].front(); ans[tot++]=c; break;}
            else num[c]--;
        }
        if(tot<k) puts("-1");
        else
        {
            REP(i,0,k-1) putchar(ans[i]);
            puts("");
        }
    }

    return 0;
}



J

题目地址:

题意

思路

代码




K

题目地址:

题意

思路

代码




L

题目地址:

题意

思路

代码




M

题目地址:

题意

思路

代码


Github下载地址:https://github.com/XLAccount/MiaoBo 项目详解地址:http://www.code4app.com/blog-843201-350.html 快速集成RTMP的视频推流教程:http://www.code4app.com/blog-843201-315.html ffmpeg常用命令操作:http://www.code4app.com/blog-843201-326.html #关于IJKMediaFramework/IJKMediaFramework.h找不到的问题,下载后直接拉到项目中即可 下载地址:https://pan.baidu.com/s/1boPOomN 密码::9yd8 #BUG修复: 解决登录程序偶尔崩溃,修复轮播图片和页面控制器叠加等问题,修复新浪授权登录 (2016.9.7) 解决程序运行中偶尔崩溃问题,解决连续下拉刷新崩溃问题,优化代码 (2016.9.8) 优化直播页面,减少不必要的性能消耗,增加用户体验 (2016.9.11) 适配5s以上的机型除了6sPlus和6Plus延迟较大外,其余延迟都较小,网速好的话可以忽略不计 (2016.9.12) 新版本极大优化程序性能,修复关注数据异常等小问题,重新布局热门页面,减少因反复加载带来的性能消耗 (2016.9.13) 增加个人中心页面,采用下拉放大图片 ➕ 波纹效果 (2016.9.14) ![image text](https://github.com/XLAccount/ALLGIFS/blob/master/psb.gif) 展示图片 ![image](https://github.com/XLAccount/ALLGIFS/blob/master/psb-1.gif) 展示图片 ![image text](https://github.com/XLAccount/ALLGIFS/blob/master/psb-2.gif) 展示图片 ![image text](https://github.com/XLAccount/ALLGIFS/blob/master/psb-3.gif) 展示图片 感谢大神Monkey_ALin http://www.jianshu.com/users/9723687edfb5/latest_articles 的demo支持
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值