2016 acm/icpc 青岛网络赛 题解(hdu 5878-5889,9道题)

5878.I Count Two Three(打表预处理,二分)

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5878

题目大意:有一些数可以写成 2a3b5c7d 的形式,称之为”I count two three numbers”.输入一个数,问比他大的最小的”I count two three numbers”是什么?

题目分析:打表预处理,发现1e9范围内的那种数只有约6000个,都列出来,然后排个序,lower_bound二分查找之。

在这里顺便总结下吧,lower_bound(start,end,n)upper_bound(start,end,n),还有binary_search(start,end,n)的区别如下:

2016.11.16 cmershen 修正:
1. lower_bound算法返回一个非递减序列[first, last)中的第一个大于等于值val的位置。
2. upper_bound算法返回一个非递减序列[first, last)中第一个大于val的位置。
3. binary_search返回true/false。

by the way,第三个函数应该很少会有人用吧~~

#include 
using namespace std;
typedef long long ll;
ll num[6000];
int t,n;
int pre() {
    int i=0;
    ll cur=1;
    //2^30,3^19,5^13,7^11 > 1e9
    for(int a=0;a<=30;a++) {
        for(int b=0;b<=19;b++) {
            for(int c=0;c<=13;c++) {
                for(int d=0;d<=11;d++) {
                    cur=pow(2,a)*pow(3,b);
                    if(cur>1e9) break;
                    cur*=pow(5,c);
                    if(cur>1e9) break;
                    cur*=pow(7,d);
                    if(cur>1e9) break;
                    num[i++]=cur;
                }
            }
        }
    }
    sort(num,num+i);
    return i;
}
int main() {
    int i=pre();
    scanf("%d",&t);
    while(t--) {
        scanf("%d",&n);
        int p=lower_bound(num,num+i,n)-num;
        printf("%d\n", num[p]);
    }
}

5879.Cure(高等数学题)

传送门:http://acm.hdu.edu.cn/showproblem.php?pid=5879

题目大意:输入n,求
k=1n1k2 ,保留五位小数。

题目分析:我们在高数中学过,(啥时候学的我怎么不记得了,捂脸)那个和收敛于
π261.64493 。(证明略,本糖也不知道~)那么就先打表枚举下,发现n取到110293的时候,前五位小数就是1.64493了,所以如果输入的n大于110293的时候,就直接输出1.64493,否则查表。

这里有个坑,就是题目中没说n多大, 所以是任意大的,要用字符串读入。

#include 
using namespace std;
typedef long long ll;
double f[110300];
char s[1111];
void pre() {
    f[0]=0.0;
    f[1]=1.0;
    ll i=2;
    while(i<110300) {
        f[i]=f[i-1]+1.0/(i*i);
        i++;
    }
}
int main() {
    pre();
    while(scanf("%s",s)!=EOF) {
        if(strlen(s)>=7) {
            printf("1.64493\n");
        }
        else {
            int a;
            sscanf(s,"%d",&a);
            if(a>=110293)
                printf("1.64493\n");
            else
                printf("%.5lf\n", f[a]);
        }
    }
}

5880.Family View(ac自动机)

http://acm.hdu.edu.cn/showproblem.php?pid=5880

题目大意:
给出一些“和谐单词”,和一段文章,将和谐单词用等长星号代替,忽略大小写。

题目分析:
一眼就看出来是ac自动机,模板题。下面的代码可以当做模板用,若注释第70行则不忽略大小写。

不熟悉ac自动机的同学请看这里的讲解:http://hihocoder.com/problemset/problem/1036

/*
*@author:Dan__ge (modified by cmershen)
*@description:AC自动机模板,输入和谐字典,和待匹配的字符串,把字符串都和谐成星号,去掉70行的注释则变成大小写不敏感!!!
*@source:hdu 5880
*/

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn=1000010;
int ans[maxn],cnt[maxn];
struct Trie{
    int next[maxn][26],fail[maxn],end[maxn];
    int root,L,ko;
    int newnode(){
        for(int i = 0;i < 26;i++)
            next[L][i] = -1;
        end[L++] = 0;
        return L-1;
    }
    void init(){
        L = 0,ko=0;
        root = newnode();
        memset(ans,0,sizeof(ans));
    }
    void insert(char buf[]){
        int len = strlen(buf);
        int now = root;
        for(int i = 0;i < len;i++){
            if(next[now][buf[i]-'a'] == -1)
                next[now][buf[i]-'a'] = newnode();
            now = next[now][buf[i]-'a'];
        }
        if(end[now]==0){
            ko++;end[now]=ko;
            cnt[ko]=len;
        }else{
            if(cnt[end[now]]void build(){
        queue<int>Q;
        fail[root] = root;
        for(int i = 0;i < 26;i++)
            if(next[root][i] == -1) next[root][i] = root;
            else{
                fail[next[root][i]] = root;
                Q.push(next[root][i]);
            }
        while( !Q.empty() ){
            int now = Q.front();
            Q.pop();
            for(int i = 0;i < 26;i++)
                if(next[now][i] == -1) next[now][i] = next[fail[now]][i];
                else{
                    fail[next[now][i]]=next[fail[now]][i];
                    Q.push(next[now][i]);
                }
        }
    }
    void query(char buf[]){
        int len = strlen(buf);
        int now = root;
        int res = 0;
        for(int i = 0;i < len;i++){
            if(buf[i]>='a'&&buf[i]<='z') now = next[now][buf[i]-'a'];
            else if(buf[i]>='A'&&buf[i]<='Z') now = next[now][buf[i]-'A'];
            else continue;
            int temp = now;
            while( temp != root ){
                ans[i+1]--;ans[i-cnt[end[temp]]+1]++;
                temp = fail[temp];
            }
        }
    }
};
char buf[maxn];
Trie ac;
int main(){
    int T,n;
    scanf("%d",&T);
    while( T-- ){
        scanf("%d",&n);
        ac.init();
        for(int i = 0;i < n;i++){
            scanf("%s",buf);
            ac.insert(buf);
        }
        ac.build();
        getchar();
        gets(buf);
        ac.query(buf);
        int len=strlen(buf),sum=0;
        for(int i=0;iif(sum<=0) printf("%c",buf[i]);
            else printf("*");
        }
        printf("\n");
    }
    return 0;
}

5881.Tea(贪心)

http://acm.hdu.edu.cn/showproblem.php?pid=5881

题目大意:给你一壶茶,你不知道茶有多少,只知道在[L,R]之间,给你两个杯子,要求两个杯子的茶水量差不超过1,杯子最后剩余的水不超过1,问至少倒几次。

题目分析:这题我有点不太理解,就直接贴下答案吧,因为他说至少倒几次是在[L,R]的最坏条件下还是最好条件下?如果考虑最好条件下,那么R值就没用了,倒两次就好了。还有就是你是不是只知道茶水什么时候剩1以下?

这道题的解释是这样的:
* 如果 R1 ,那么不用倒直接满足题意。
* 如果 R=2 ,那就倒1次,这里我有个问题,就是如果是[0,2]这个输入,那么你是有可能倒不出来的啊。。。。。。。
* 如果 RL1 ,那么就倒两次,每次倒L/2,这样剩的肯定不超过1.
* 否则,先倒两次,分别是 (L1)/2,(L+1)/2 ,再一边倒2.

#include 
using namespace std;
typedef long long ll;
ll l,r;
int main() {
    while(scanf("%I64d %I64d",&l,&r)!=EOF) {
        ll ans;
        if(r<=1)
            ans=0;
        else if(r<=2)
            ans=1;
        else if(r-l<=1)
            ans=2;
        else {
            if(l<=1) l=1;
            ans=(r-l)/2+1;
        }
        printf("%I64d\n", ans);
    }
}

5882.Balanced Game(水题)

http://acm.hdu.edu.cn/showproblem.php?pid=5882

题目大意:
问n个手势的石头剪子布是否公平?

题目分析:
说实话这题我也没彻底理解,因为从直觉上看,就是奇数公平偶数不公平,而且偶数不公平显然如此。

but,如何证明奇数一定公平?有没有一种对任意n均公平的分配方案?这个有待进一步探讨啊~~
2016.11.16 cmershen add
知乎上有大神回复了,可以这样设计方案:
在圆周上等距离分布2n+1个点,对任意一个点,顺时针数n个点赢他,逆时针数n个点输他,这样就可以保证任意两种不同手势均能分出胜负,且完全公平。

#include 
using namespace std;
typedef long long ll;
int t,n;
int main() {
    scanf("%d",&t);
    while (t--) {
        scanf("%d",&n);
        if (n&1)
            printf("Balanced\n");
        else
            printf("Bad\n");
    }
}

5883.The Best Path(欧拉回路)

http://acm.hdu.edu.cn/showproblem.php?pid=5883

题目大意:n个点,m个边的无向图,每个点有权值 ai ,问可不可以每条边只走一遍,遍历所有边?如果可以,则求出路径上经过的所有点(可以重复)异或的最大值,如果不可以,输出Impossible

题目分析:
求欧拉路咯,小学奥数都学过,看有几个度为奇数的点。如果是0个,则是欧拉回路,如果是2个,则是欧拉通路。

接下来就看每个点要经过几次:

如果度为n,那么这n条边都要遍历一次,而每走其中两条边,就要经过这个点一次(因为要来到这个点,还得出去),如果剩下一条边,那么也要走一次这个点,因为它必是起点/终点。

根据异或的性质,如果这个次数值是奇数,那就加到答案里,如果是偶数,那就消掉了。

那么如果该图存在的是欧拉通路,那答案就是唯一的,如果是欧拉回路,相当于起点多走了一次,枚举起点即可。

这题的数据我觉得有点弱,因为还需要考虑整个图是否连通的问题,但是没考虑也过了。

#include 
using namespace std;
typedef long long ll;
int t,n,m,u,v;
short a[100005];
int deg[100005];
vector<int> g[100005];
int main() {
    scanf("%d",&t);
    while (t--) {
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++)
            g[i].clear();
        memset(deg,0,sizeof(deg));
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=0;iscanf("%d %d",&u,&v);
            g[u].push_back(v);
            g[v].push_back(u);
            deg[u]++;deg[v]++;
        }
        int cnt=0,sum=0;
        for(int i=1;i<=n;i++) {
            if(deg[i]&1)
                cnt++;
            int temp=(deg[i]+1)/2;
            if(temp & 1)
                sum^=a[i];
        }
        if(cnt==0) { //Eular Curcuit
            int ans=0;
            for(int i=1;i<=n;i++)
                ans=max(ans,sum^a[i]);
            printf("%d\n", ans);
        }
        else if(cnt==2)  //Eular Path
            printf("%d\n", sum);
        else
            printf("Impossible\n");
    }
}

5884. sort(huffman树)

http://acm.hdu.edu.cn/showproblem.php?pid=5884

题目大意:
给你n个正整数和最大花费T,每次可以合并不超过k个数,花费是这k个数的和,最后合并成一个数,求不超过花费T的情况下,最小的k。

题目分析:
其实这就是一个k叉Huffman树求带权路径长度的问题。首先考虑到我们要用k-1次操作合并n-1个数,那么当
(k1)%(n1)!=0 时,最后剩下的数就会不到k个,所以我们先将多出来的部分合并成一个,显然先合并要比后合并花费小。
接下来就是求这个最小的k,因为k的取值范围确定为 [2,n] ,且花费 cost(k) 对k单调递减(这个我不知道咋证。。。直觉如此),因此在 [2,n] 上二分答案就ok了。

如果你就这么做下去,那么你肯定tle了。

这题卡了logn(大概在6左右),直接用优先队列的复杂度是 O(lognnlogn) ,因此要优化一下。
用两个队列q1和q2,其中q1装原来的数(排序好),q2装合并后的数,根据Huffman树性质,合并数肯定是越合越大,所以q2也是单调的。那么每次先从q1和q2里加一起取k个数,合并起来扔q2里,直到q1取完,再取q2,直到q2里剩一个数。这样做就能过了,时间在1s左右。

#include 
using namespace std;
typedef long long ll;
int t0,t,n;
int a[100005];
int cost(int x) {
    queue<int> q1,q2;
    int ans=0;
    sort(a,a+n);
    for(int i=0;iif((n-1)%(x-1)!=0) {
        int temp=(n-1)%(x-1)+1;
        int sum=0;
        for(int j=0;jwhile(!q1.empty()) {
        int sum=0;
        for(int j=0;jif(!q1.empty() && !q2.empty()) {
                if(q1.front()else {
                    sum+=q2.front();
                    q2.pop();
                }
            }
            else if(q1.empty()) {
                sum+=q2.front();
                q2.pop();
            }
            else if(q2.empty()) {
                sum+=q1.front();
                q1.pop();
            }
        }
        ans+=sum;
        q2.push(sum);
    }
    while(q2.size()>1) {
        int sum=0;
        for(int i=0;ireturn ans;
}
int main() {
    scanf("%d",&t0);
    while(t0--) {
        scanf("%d %d",&n,&t);
        for(int i=0;iscanf("%d",&a[i]);
        }
        int l=2,r=n;
        while(lint mid=(l+r)/2;
            if(cost(mid)<=t)
                r=mid;
            else
                l=mid+1;
        }
        printf("%d\n", l);
    }
}

5887. Herbs Gathering(dfs解01背包+剪枝)

http://acm.hdu.edu.cn/showproblem.php?pid=5887

题目大意:
01背包,100个物品,体积和背包容量可达1e9。

题目分析:

我们传统的用DP解背包都是物品多,背包容量M有限,这样可以用DP做,在 O(nm) 内就能搞定了。

但此题的M太大了,N又很小,所以用搜索来解。但 2100 的搜索空间也不行啊,所以剪枝吧~

这道题我见到的黑科技很多,最极端的是用时间剪枝,就是一个dfs跑到10ms就return,居然也能过。。。。。。。。

我采用的是按性价比剪枝,也就是先按性价比排序,如果背包剩下的空间里全装这个东西也没有更优解,则停止搜索(因为后面的性价比低,就更不可能找到解了),这样就0ms AC了。

#include 
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
int n,t;
typedef struct  {
    int t;//time
    int s;//score
    double r;//rate
}herb;
herb h[105];
bool cmp(herb x,herb y) {
    return x.r>y.r;
}
ll ans;
void dfs(int i,ll cur,ll sc) { //决定第i个物品放不放,当前背包容量为cur,得分为sc
    if(sc>ans)
        ans=sc;
    if(sc+h[i].r*(t-cur)return;//剩下的容积就算全放i,也得不到更优解,更何况后面的性价比更低呢
    if(iif(h[i].t<=t-cur) //第i个物品装得下
            dfs(i+1,cur+h[i].t,sc+h[i].s);
        dfs(i+1,cur,sc);//不放i
    }
}
int main() {
    RE("in.txt");
    WR("out.txt");
    while(scanf("%d %d",&n,&t)!=EOF) {
        for(int i=0;iscanf("%d %d",&h[i].t,&h[i].s);
            h[i].r=(double)(h[i].s)/(double)(h[i].t);
        }
        ans=0;
        sort(h,h+n,cmp);
        dfs(0,0,0);//决定第0个物品,目前背包里容量为0,得分为0
        printf("%I64d\n", ans);
    }
}

5889.Barricade(最短路径+网络流)

http://acm.hdu.edu.cn/showproblem.php?pid=5889

题目大意:

有个地图,n个点,m个边,无向图,每条边的长度都一样。

你在1点,敌人在n点,已知敌人一定按最短路线来找你,所以你要将敌人的路设置障碍,使得敌人不管沿什么路径过来,都会遇到你的障碍。又已知在第i条边设置障碍需要cost[i]的花费,求至少多少花费才能使敌人全部遇到障碍。

题目分析:

首先在原图上跑一遍dijkstra算法,求1点到其他点的最短路,然后构造一张新图,在最短路的路径上(条件是dis[i]-dis[j]==1 && i,j有边)加边,边权为设置障碍的花费。然后新图上以1为源,n为汇点跑最大流即可。

这里分别给出dinic算法和SAP算法的AC代码,供当做模板用,其中sap算法用时15ms,dinic算法用时46ms。

//sap,15ms
#include 
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
#define INF 0x3f3f3f3f
int t,u,v,w,n,m;
int cost[1005][1005];
int dis[1005];

struct EDGE {
    int u,v,cap;
    int next;
}edge[20005];
int head[1005],p;
int gap[1005],dep[1005],cur[1005],stk[1005];
void addedge(int u,int v) {
    int c=cost[u][v];
    edge[p].u=u;edge[p].v=v;edge[p].cap=c;
    edge[p].next=head[u]; head[u]=p++;

    edge[p].u=v;edge[p].v=u;edge[p].cap=0;
    edge[p].next=head[v]; head[v]=p++;
}
void dijkstra(int u) { //u点为源,求单源最短路径
    bool vis[1005];
    for(int i=1;i<=n;i++) {
        if(cost[i][u]!=INF && i!=u)
            dis[i]=1;
        else
            dis[i]=INF;
        vis[i]=false;
    }
    dis[u]=0;
    vis[u]=true;
    for(int i=1;i<=n;i++) {
        int min=INF;
        int x;
        for(int j=1;j<=n;j++) {
            if(!vis[j] && dis[j]true;
        for(int j=1;j<=n;j++)
            if(!vis[j] && cost[x][j]!=INF && min+11;
    }
}
void bfs(int t) {
    memset(dep,-1,sizeof(dep));
    memset(gap,0,sizeof(gap));
    queue<int> q;
    dep[t]=0;
    gap[0]=1;
    q.push(t);
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        for(int i=head[u];i!=-1;i=edge[i].next) {
            int v=edge[i].v;
            if(edge[i^1].cap>0 && dep[v]==-1) {
                q.push(v);
                dep[v]=dep[u]+1;
                gap[dep[v]]++;
            }
        }
    }
}
int sap(int s,int t) {
    bfs(t);
    memcpy(cur,head,sizeof(cur));
    int ans=0;
    int u=s,top=0,i;
    while(dep[s]if(u==t) {
            int delta=INF;
            int flag=n;
            for(i=0;i!=top;i++) {
                if(delta>edge[stk[i]].cap) {
                    delta=edge[stk[i]].cap;
                    flag=i;
                }
            }
            for(i=0;i!=top;i++) {
                edge[stk[i]].cap-=delta;
                edge[stk[i]^1].cap+=delta;
            }
            ans+=delta;
            top=flag;
            u=edge[stk[top]].u;
        }
        for(i=cur[u];i!=-1;i=edge[i].next) {
            int v=edge[i].v;
            if(edge[i].cap>0 && dep[u]==dep[v]+1)
                break;
        }
        if(i!=-1) {
            cur[u]=i;
            stk[top++]=i;
            u=edge[i].v;
        }
        else {
            if(--gap[dep[u]]==0)
                break;
            int mind=n+1;
            for(i=head[u];i!=-1;i=edge[i].next) {
                if(edge[i].cap>0 && mind>dep[edge[i].v]) {
                    mind=dep[edge[i].v];
                    cur[u]=i;
                }
            }
            dep[u]=mind+1;
            gap[dep[u]]++;
            u=(u==s)?u:edge[stk[--top]].u;
        }

    }
    return ans;
}
int main() {
    scanf("%d",&t);
    while(t--) {
        scanf("%d %d",&n,&m);
        memset(cost,0x3f,sizeof(cost));
        memset(head,-1,sizeof(head));
        p=0;
        for(int i=1;i<=m;i++) {
            scanf("%d %d %d",&u,&v,&w);
            cost[u][v]=w;
            cost[v][u]=w;
        }
        dijkstra(1);
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=n;j++) {
                if(dis[i]+1==dis[j] && cost[i][j]!=INF)
                    addedge(i,j);
            }
        }
        printf("%d\n", sap(1,n));        //源点1,汇点n,求最大流
    }

}
//Dinic,46ms
#include 
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
typedef long long ll;
#define INF 0x3f3f3f3f
int t,u,v,w,n,m;
int cost[1005][1005];
int dis[1005];

struct EDGE {
    int to,next,flow;
}edge[20005];
int head[10005],p,level[10005];
void addedge(int u,int v) {
    int w=cost[u][v];
    edge[p].to=v;
    edge[p].flow=w;
    edge[p].next=head[u];
    head[u]=p;p++;

    edge[p].to=u;
    edge[p].flow=0;
    edge[p].next=head[v];
    head[v]=p;p++;
}
void dijkstra(int u) { //u点为源,求单源最短路径
    bool vis[1005];
    for(int i=1;i<=n;i++) {
        if(cost[i][u]!=INF && i!=u)
            dis[i]=1;
        else
            dis[i]=INF;
        vis[i]=false;
    }
    dis[u]=0;
    vis[u]=true;
    for(int i=1;i<=n;i++) {
        int min=INF;
        int x;
        for(int j=1;j<=n;j++) {
            if(!vis[j] && dis[j]true;
        for(int j=1;j<=n;j++)
            if(!vis[j] && cost[x][j]!=INF && min+11;
    }
}
bool bfs(int s,int t) {
    memset(level,0,sizeof(level));
    queue<int> q;
    q.push(s);
    level[s]=1;
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        if(t==u)
            return true;
        for(int i=head[u];i!=-1;i=edge[i].next) {
            int v=edge[i].to,f=edge[i].flow;
            if(level[v] == 0 && f!=0) {
                q.push(v);
                level[v]=level[u]+1;
            }
        }
    }
    return false;
}
int dfs(int u,int maxf,int t) {
    if(u==t)
        return maxf;
    int temp=0;
    for(int i=head[u];i!=-1&&tempint v=edge[i].to,f=edge[i].flow;
        if(level[v] == level[u]+1 && f!=0) {
            f=dfs(v,min(maxf-temp,f),t);
            edge[i].flow-=f;edge[i^1].flow+=f;temp+=f;
        }
    }
    if(!temp)
        level[u]=INF;
    return temp;
}
int dinic(int s,int t) { //源点s,汇点t,求最大流
    ll ans=0;
    while(bfs(s,t))
        ans+=dfs(s,INF,t);
    return ans;
}
int main() {
    scanf("%d",&t);
    while(t--) {
        scanf("%d %d",&n,&m);
        memset(cost,0x3f,sizeof(cost));
        memset(head,-1,sizeof(head));
        p=0;
        for(int i=1;i<=m;i++) {
            scanf("%d %d %d",&u,&v,&w);
            cost[u][v]=w;
            cost[v][u]=w;
        }
        dijkstra(1);
        for(int i=1;i<=n;i++) {
            for(int j=1;j<=n;j++) {
                if(dis[i]+1==dis[j] && cost[i][j]!=INF)
                    addedge(i,j);
            }
        }
        printf("%d\n", dinic(1,n));        //源点1,汇点n,求最大流
    }

}

你可能感兴趣的:(another,oj)