multiset 第一次用,太神奇了

题意:push a 把a压栈;pop b 把小于等于b的所有元素中最大的元素的出栈,若没有则输出“No Element!”!

View Code
#include<iostream>  
#include<stdio.h>  
#include<set>  
using namespace std;  
int main()  
{  
    multiset<int> st;  
    multiset<int>::iterator it;  
    int n;  
    char op[5];  
    int tp;  
    while(scanf("%d",&n)!=EOF)  
    {  
        st.clear();  
        while(n--)  
        {  
            scanf("%s%d",op,&tp);  
            if(op[1]=='u')  
                st.insert(tp); 
            else 
            {  
                it=st.upper_bound(tp);//返回容器元素中大于tp的迭代器  
                if(it==st.begin()) //等于begin() 表示不存在小于等于tp的元素
                    printf("No Element!\n");  
                else
                {  
                    it--;  
                    if(*it<=tp)  
                    {  
                        printf("%d\n",*it);  
                        st.erase(it);  
                    }  
                    else 
                        printf("No Element!\n");  
                }  
            }  
        } 
        printf("\n");  
    }  
}

 

posted @ 2012-05-25 22:32 枕边梦 阅读(0) 评论(0) 编辑

题意:题目的意思很清晰,对于一个有向图,将N个点划分成最少的集合个数,同时满足俩个条件:

1) 任意俩点,若互相可达,则必须在同一个集合中

2)属于同一个集合的任意俩个点对(u,v),至少存在一条路径,使得v对于u 可达 或者 u 对于v 可达

分析:对于上述俩个条件,为了简化问题,需要进行缩点,属于同一个强连通分量的点,缩成同一个点,重新构图,可以用tarjan 算法;

这样,第一个条件就一定满足了,接着只剩下第二个条件了,其实,任意俩点,只要在同一条有向路径上,则可以属于一个集合,,,那么问题就转化为用最小的有向路径去覆盖所有的点(最小路径覆盖数==点数-最大匹配数)

View Code
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
const int N = 5000+10;
vector<int> g[N],g2[N];
stack<int> st; 
int n,dfn[N],low[N],f[N],index; 
int num,match[N]; 
bool vis[N],instack[N]; 
void tarjan(int u)//求强连通分支
{ 
    int v; 
    dfn[u] = low[u] = index++;  
    st.push(u);   
    instack[u] = true; 
    vis[u] = true; 
    for(int i=0; i<g[u].size(); i++) 
    {     
        v = g[u][i];       
        if(!vis[v]) 
        {               
            tarjan(v);       
            low[u] = min(low[u], low[v]);  
        }          
        else if(instack[v]) 
            low[u] = min(low[u], dfn[v]); 
    }    
    if(dfn[u] == low[u]) 
    {             
        do 
        {           
            v = st.top();             
            instack[v] = false; 
            st.pop();         
            f[v]=num;//记录每一个点所在的强连通分支   
        } 
        while(v != u); 
        num++;  
    }
}
int path(int s)
{
    vector<int>::iterator it=g2[s].begin();
    for(;it!=g2[s].end();it++)
    {
        int v=*it;
        if(!vis[v])
        {
            vis[v]=true;
            if(match[v]==-1 || path(match[v]))
            {
                match[v]=s;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    int T,cas=0,m,a,b;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d %d",&n,&m);
        for(int i=0;i<=n;i++) 
        {
            g[i].clear();
            g2[i].clear();
        }
        while(m--) 
        {
            scanf("%d %d",&a,&b); 
            g[a].push_back(b);
        }
        memset(vis,false,sizeof(vis)); 
        memset(instack,false,sizeof(instack)); 
        index=num=0; 
        for(int i=1;i<=n;i++) 
        {
            if(!vis[i]) 
                tarjan(i);
        }
        for(int i=1;i<=n;i++) //重新构图
            for(int j=0;j<g[i].size();j++) 
            {
                if(f[i]!=f[g[i][j]]) 
                {
                    g2[f[i]].push_back(f[g[i][j]]);
                }
            }
        memset(match,-1,sizeof(match));
        int ans=0;
        for(int i=0;i<num;i++)//求最大匹配数
        {
            memset(vis,false,sizeof(vis));
            ans+=path(i);
        }
        printf("%d\n",num-ans);
    }
    return 0;
}

 

 

posted @ 2012-05-19 20:49 枕边梦 阅读(8) 评论(0) 编辑

题意: 给定一个有向图,有路径(边)权值和节点的权值,求一个字典序最小的最短路径(中间节点权值+路径权值)

分析:此题目需要对floyd算法有比较深的了解,首先floyd是一个不断拓展路径的过程,同时也是不断增加中间节点的过程,所以累加中间节点权值的部分很好处理。

理解了floyd 算法的过程之后,记录路径也非难事,关键是题目要求的是字典序最小的最短路径。

记录路径有俩种方式:

1)

path[i][j] 记录 起点为i ,终点为j 的路径上j 的直接前驱

View Code
void init(int n)
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&g[i][j]);
            if(i!=j && g[i][j]!=-1)
                path[i][j]=i;
            else path[i][j]=-1;
        }
    for(int i=1;i<=n;i++)
        scanf("%d",&b[i]);
}
void floyd(int n)
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
        {
            if(i==k || g[i][k]==-1) continue;
            for(int j=1;j<=n;j++)
            {
                if(i==j || j==k) continue;
                if(g[k][j]==-1)
                    continue;
                int newdis=g[i][k]+g[k][j]+b[k];//k为中间节点
                if(g[i][j]==-1 || g[i][j]>newdis)
                {
                    g[i][j]=newdis;
                    path[i][j]=path[k][j];
                }
                else if(g[i][j]==newdis && path[i][j]>path[k][j])
                    path[i][j]=path[k][j];
            }
        }
}

事实上,上面这种方法并不能保证字典序,但不能否定的是,这是一种记录路径的方法
为什么不能保证字典序呢?看一下更新路径的代码:

if(g[i][j]==newdis && path[i][j]>path[k][j])
                    path[i][j]=path[k][j];

更新的是j的直接前驱,其实也很明显了,单纯比较j 的直接并不能保证字典序的大小。。因为i的直接后继才是关键

2)

path[i][j] 记录起点为i ,中终点为j 的路径上i 的直接后继

View Code
inline void init(int n)
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&g[i][j]);
            path[i][j]=j;
        }
    for(int i=1;i<=n;i++)
        scanf("%d",&b[i]);
}
inline void floyd(int n)
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
        {
            if(i==k || g[i][k]==-1) continue;
            for(int j=1;j<=n;j++)
            {
                if(i==j || j==k) continue;
                if(g[k][j]==-1)
                    continue;
                int newdis=g[i][k]+g[k][j]+b[k];
                if(g[i][j]==-1 || g[i][j]>newdis)
                {
                    g[i][j]=newdis;
                    path[i][j]=path[i][k];
                }
                else if(g[i][j]==newdis && path[i][j]>path[i][k])
                    path[i][j]=path[i][k];
            }
        }
}

这种方法就保证了字典序,因为每次拓展路径的时候都是保证起点的直接后继字典序,这样的路径拼接的时候才能保证字典序最小

看完整的代码吧,还有要注意的一点是,数据中有可能出现起点等于终点的情况

View Code
#include<iostream>
#include<algorithm>
#include<stack>
using namespace std;
const int N = 50+10;
int path[N][N],g[N][N];
int b[N];
inline void init(int n)
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            scanf("%d",&g[i][j]);
            path[i][j]=j;
        }
    for(int i=1;i<=n;i++)
        scanf("%d",&b[i]);
}
inline void floyd(int n)
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
        {
            if(i==k || g[i][k]==-1) continue;
            for(int j=1;j<=n;j++)
            {
                if(i==j || j==k) continue;
                if(g[k][j]==-1)
                    continue;
                int newdis=g[i][k]+g[k][j]+b[k];
                if(g[i][j]==-1 || g[i][j]>newdis)
                {
                    g[i][j]=newdis;
                    path[i][j]=path[i][k];
                }
                else if(g[i][j]==newdis && path[i][j]>path[i][k])
                    path[i][j]=path[i][k];
            }
        }
}
inline void printpath(int s,int t)
{
    printf("Path: %d",s);
    while(path[s][t]!=t)
    {
        printf("-->%d",path[s][t]);
        s=path[s][t];
    }
    printf("-->%d\n",t);
}
int main()
{
    int n,s,t;
    while(scanf("%d",&n)==1 && n)
    {
        init(n);
        floyd(n);
        while(scanf("%d %d",&s,&t)==2)
        {
            if(s==-1 && t==-1)
                break;
            printf("From %d to %d :\n",s,t);
            if(s==t)
            {
                printf("Path: %d\n",s);
                printf("Total cost : 0\n\n");
                continue;
            }
            printpath(s,t);
            printf("Total cost : %d\n\n",g[s][t]);
        }
    }
    return 0;
}

 

 

posted @ 2012-05-05 14:21 枕边梦 阅读(16) 评论(2) 编辑

经典的树形游戏的博弈

题目给定一棵树,虽然是规定给定一棵根为1树,但数据给的是树枝,所以是无向的,坑爹啊

View Code
#include<iostream>
#include<algorithm>
#include<string>
#include<vector>
using namespace std;
const int N = 100000+10;
vector<int> g[N];
bool vis[N];
int w[N];
void dfs(int u)
{
    vis[u]=true;
    int size=g[u].size();
    w[u]=0;
    for(int i=0;i<size;i++)
    {
        int v=g[u][i];
        if(vis[v]) continue;
        dfs(v);
        w[u]^=(w[v]+1);
    }
}
int main()
{
    int T,n;
    int a,b;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=0;i<=n;i++)
            g[i].clear();
        for(int i=1;i<n;i++)
        {
            scanf("%d %d",&a,&b);
            g[a].push_back(b);
            g[b].push_back(a);
        }
        memset(vis,false,sizeof(vis));
        dfs(1);
        if(w[1])
            puts("Alice");
        else puts("Bob");
    }
    return 0;
}

 

posted @ 2012-04-20 13:46 枕边梦 阅读(7) 评论(0) 编辑

题意很浅显,给定当前下载任务的已选状态和最终状态,可以当过单选,全选,反选等三个操作达成,问最少的操作数

分析:思维真的有很大的局限性,怎么就没想到去整理一下规律呢,一心只想着搜索,dp呀,虽然这俩方面都很水……

看了大牛的解释,我也跟着恍然大悟了……

View Code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    char S[55],T[55];
    int i,j;
    int Min,count[4],n;
    while (scanf("%d",&n)!=EOF)
    {
        memset(count,0,sizeof(count));
        scanf("%s",S);
        scanf("%s",T);
        for (i = 0; i < n; i++)
        {
            if (S[i]!=T[i])
                count[0]++;    //记录不同的位,对直接改变这些位需要的操作
            else
                count[1]++;    //记录相同的位,反选一次后改变这些位
            if(T[i]=='0') count[2]++; //目标序列中0的位,就是不需要选择的TV,全选后复原这些位需要的操作
        }
        //count[0]就是直接点击选择需要的总操作次数
        count[1]++; 
        //count[1]为反选一次后再作选择总操作,增加一次反选操作
        count[2]++; 
        //count[2]是全选一次后取消选择总操作,count[2]为全选后取消选择需要的操作,增加一次全选操作
        count[3] = 2+n-(count[2]-1);
        //n-(count[2]-1)等于需要选择看的TV总数 ,再+2表示全选反选归零后重新选择的做法

       //count[0]直接选择是理所当然的做法。

       //其实关键是需要考虑到,对于全选和反选,都是只是做一次的,或者各做一次。

       //这个很容易可以证明,因为两次反选后对每一位来说,状态又回到原来的去,相当于多花费两次操作。

       //每次全选就等于回到到第一次的全选了。

       //全选之后反选归零。

       //其实联想到逻辑代数里的一些关系,还是挺直观的。。。为什么当时就没想到!!!!

        Min = count[0];

        for (i = 1; i < 4; i++)

            Min = (count[i]<Min?count[i]:Min);

        printf("%d\n",Min);

    }

    return 0;

}

代码来自此博客http://blog.sina.com.cn/s/blog_626631420100tjzv.html

posted @ 2012-04-17 21:11 枕边梦 阅读(12) 评论(0) 编辑
摘要: pku2899 Jamie's Contact Groups题意:在通讯录中有N个人,每个人能可能属于多个group,现要将这些人分组m组,设各组中的最大人数为max,求出该最小的最大值分析:二分group的最大值,+二分图多重匹配View Code #include<iostream>#include<algorithm>#include<string>using namespace std;const int N = 1000+10;const int M = 500+10;int n,m;int map[N][M],vlink[M],link[阅读全文
posted @ 2012-04-15 15:51 枕边梦 阅读(24) 评论(0) 编辑
摘要: 很明显的一个二分图多重匹配,一开始用网络流直接建图,悲剧的TLE了之后,看了网上解释,缩点,确实大大简化了问题,也就可以AC了,600+ms一开始是这样建图的,先有虚拟源点s,汇点t,从s向每一个人连一条容量为1的边,每一个人向他适合的每一个planet连一条容量为1边,再从每一个planet连一条容量为w[i](该planet的容量)的边,很明显,判断最大流是否等于n即可。n为100000,而m只有10其实,在这道题目里面,人是无差别的,有区别是他们各自的选择,而总共只有10个planet,也就是所有的选择数也就(1<<10)种,所有选择都相同的人完全是等价的!!!可是,没想到还阅读全文
posted @ 2012-04-15 14:34 枕边梦 阅读(22) 评论(0) 编辑
摘要: 网上很多用线段树做的,,好复杂,,,这个是用纯STL做的,map+set转自http://blog.csdn.net/zz_1215/article/details/7318800View Code #include<iostream>#include<vector>#include<algorithm>#include<string>#include<map>#include<set>using namespace std;map<int, set<int> > m;//x映射到一个y坐标的集合m阅读全文
posted @ 2012-04-15 09:35 枕边梦 阅读(11) 评论(0) 编辑
摘要: 题意:给定一个矩形,将矩形切成给定任意个数的小矩形,当然每一个小矩形都有对应的权值,求最大权值分析:其实,就是一个类似完全背包的问题dp[i][j]表示从(0,0)到(i,j)对应的矩形可以切出小矩形的最大权值和View Code #include<iostream>#include<algorithm>#include<string>using namespace std;const int N = 1000+10;struct rec{ int x,y; int p;}r[15];int dp[N][N];int main(){ int T,n,X,Y;阅读全文
posted @ 2012-04-14 20:49 枕边梦 阅读(20) 评论(0) 编辑
摘要: 题意:求出1到N 的无向图的无重复边的最短路径数(即所有的最短路径没有公共边)分析:先求出最短路,再找出所有属于最短路的边(满足dist[u]+g[u][v]==dist[v]),把有向边(u,v)加入到新图中,容量为1,(即每条边只能用一次),最后求一次源点为1,汇点为t的最大流即可View Code #include<iostream>#include<algorithm>#include<string>#include<vector>#include<stack>using namespace std;const int N =阅读全文
posted @ 2012-04-13 19:42 枕边梦 阅读(35) 评论(0) 编辑