bzoj1124[POI2008]枪战maf tarjan+树规+贪心/线性DP

这代码快写死我了.....死人最多随便推推结论。死人最少,每个环可以单独考虑,每个环上挂着的每棵树也可以分别考虑.tarjan找出所有环,对环上每个点,求出选它和不选它时以它为根的树的最大独立集(就是最多活下来的人数),然后环上每个点选或不选对应的是一个“价值”,这个价值是那个点挂着的树里最多存活人数。先全都不选环上的点,算出选和不选时最大独立集的差值,问题变成有一个环,环上有一堆数(那些差值),选出一些不相邻的数使得和最大,然后我按着bzoj2151种树写了个贪心....这个贪心的思路也很神,网上有很多题解.后来发现这个东西和“种树”还不一样,因为种树限制必须种m棵,这个选的个数不限,所以可以O(nlogn)降到O(n),不过这两种方法都能过。另外tarjan不加inline会RE.总之这个方法不太具备可写性.

#include<cstdio>
#include<queue>
#include<vector>
using namespace std;
const int maxn=1500005;
int aim[maxn];
struct edge{//反向建边
    int to,next;
}lst[maxn],lst2[maxn];int len=1,len2=1;
int first[maxn],first2[maxn];
void addedge(int a,int b){
    lst[len].to=b;
    lst[len].next=first[a];
    first[a]=len++;
}
void addedge2(int a,int b){
    lst2[len2].to=b;
    lst2[len2].next=first2[a];
    first2[a]=len2++;
}
int T;
int s[maxn],low[maxn],dfn[maxn],indeg[maxn],top;
int belong[maxn],sz[maxn],cnt;
bool ins[maxn];
inline void tarjan(int x){
    dfn[x]=low[x]=++T;
    s[top++]=x;ins[x]=true;
    if(!dfn[aim[x]]){
        tarjan(aim[x]);
        if(low[aim[x]]<low[x])low[x]=low[aim[x]];
    }else if(ins[aim[x]]&&dfn[aim[x]]<low[x])low[x]=dfn[aim[x]];
    if(low[x]==dfn[x]){
        ++cnt;
        do{
            ins[s[--top]]=false;
            belong[s[top]]=cnt;
            sz[cnt]++;
            addedge2(cnt,s[top]);
        }while(s[top]!=x);
    }
}
bool die[maxn];
int f[2][maxn];//bool vis[2][maxn];//f[][]求活人数目
int max(int a,int b){
    return a>b?a:b;
}
int q[maxn];bool vis[maxn];
void dp(int s){
    int head=0,tail=0;
    if(vis[s])return;
    q[tail++]=s;
    while(head!=tail){
        int x=q[head++];vis[x]=true;
        for(int pt=first[x];pt;pt=lst[pt].next){
            if(!vis[lst[pt].to])q[tail++]=lst[pt].to;
        }
    }
    for(int i=tail-1;i>=0;--i){
        int x=q[i];
        f[1][x]=1;
        for(int pt=first[x];pt;pt=lst[pt].next){
            if(belong[lst[pt].to]==belong[x])continue;
            f[1][x]+=f[0][lst[pt].to];
            if(die[lst[pt].to])f[0][x]+=f[0][lst[pt].to];
            else f[0][x]+=max(f[0][lst[pt].to],f[1][lst[pt].to]);
        }
    }
}
int w[maxn];/*
int work(int num){//第num个SCC最多生还多少人。种树???
    ++T;
    priority_queue<node> q;
    int ans=0;//猜测SCC节点出栈是按照在环上的顺序
    for(int i=1;i<=sz[num];++i){
        nxt[i]=i+1;
        pre[i]=i-1;
    }
    nxt[sz[num]]=1;pre[1]=sz[num];
    int j=0;
    for(int pt=first2[num];pt;pt=lst2[pt].next){
         
        ans+=f[0][lst2[pt].to];
        if(!die[lst2[pt].to])w[++j]=f[1][lst2[pt].to]-f[0][lst2[pt].to];
        else w[++j]=0;
        q.push(node(j));
    }//for(int i=1;i<=j;++i)printf("%d ",w[i]);printf("\n");
    while(!q.empty()){//最后一次拿出来的时候会出错吗?
        node tmp=q.top();q.pop();
        if(used[tmp.k]==T)continue;
        if(w[tmp.k]<=0)break;
        ans+=w[tmp.k];//printf("%d %d\n",tmp.k,w[tmp.k]);
        w[tmp.k]=w[pre[tmp.k]]+w[nxt[tmp.k]]-w[tmp.k];//printf("w%d\n",w[tmp.k]);
        used[pre[tmp.k]]=used[nxt[tmp.k]]=T;
        if(pre[tmp.k]==nxt[tmp.k])break;
        pre[tmp.k]=pre[pre[tmp.k]];nxt[tmp.k]=nxt[nxt[tmp.k]];
        if(used[pre[tmp.k]]==T||used[nxt[tmp.k]]==T)break;
        pre[nxt[tmp.k]]=tmp.k;nxt[pre[tmp.k]]=tmp.k;// printf("%d %d %d %d\n",w[1],w[2],w[3],w[4]);
        q.push(node(tmp.k));
    }//printf("%d\n",ans);
    return ans;
}*/
int F[2][2][maxn];
int work(int num){
    int j=0,ans=0;
    for(int pt=first2[num];pt;pt=lst2[pt].next){
        ans+=f[0][lst2[pt].to];
        if(!die[lst2[pt].to])w[++j]=f[1][lst2[pt].to]-f[0][lst2[pt].to];
        else w[++j]=0;
    }
    F[1][1][1]=w[1];F[0][0][1]=0;F[0][1][1]=F[1][0][1]=-0x3f3f3f3f;
    for(int i=2;i<=j;++i){
        F[0][1][i]=F[0][0][i-1]+w[i];
        F[1][1][i]=F[1][0][i-1]+w[i];
        F[0][0][i]=max(F[0][0][i-1],F[0][1][i-1]);
        F[1][0][i]=max(F[1][0][i-1],F[1][1][i-1]);
    }
    return ans+max(F[0][0][j],max(F[0][1][j],F[1][0][j]));
}
int main(){
    int n;scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%d",aim+i);
        //if(i==aim[i])die[i]=true;
        //else
         addedge(aim[i],i);
    }
    for(int i=1;i<=n;++i){
        if(!dfn[i])tarjan(i);
    }
    for(int i=1;i<=n;++i){
        if(belong[i]!=belong[aim[i]])indeg[belong[aim[i]]]++;
        if(i==aim[i])indeg[belong[i]]++;
    }
    for(int i=1;i<=n;++i){
        if(!first[i]){
            die[aim[i]]=true;
        }
    }
    for(int i=1;i<=n;++i){
        dp(i);
    }
    int ans1=n;
    for(int i=1;i<=n;++i){
        if(aim[i]==i){
            ans1-=f[0][i];
        }
    }
    for(int i=1;i<=cnt;++i){
        if(sz[i]>1){
            ans1-=work(i);
        }
    }
    int ans2=n;
    for(int i=1;i<=cnt;++i){
        if(!indeg[i])ans2--;
    }
    printf("%d %d\n",ans1,ans2);
    return 0;
}

  

时间: 2025-01-12 06:47:15

bzoj1124[POI2008]枪战maf tarjan+树规+贪心/线性DP的相关文章

BZOJ1124: [POI2008]枪战Maf

1124: [POI2008]枪战Maf Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 501  Solved: 200[Submit][Status][Discuss] Description 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺序开枪,且任意时刻只有一个人开枪.因此,对于不同的开枪顺序,最后死的人也不同. Input 输入n人数<1000000 每个人的aim Output 你要求最

【BZOJ1124】[POI2008]枪战Maf 贪心+思路题

[BZOJ1124][POI2008]枪战Maf Description 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺序开枪,且任意时刻只有一个人开枪.因此,对于不同的开枪顺序,最后死的人也不同. Input 输入n人数<1000000 每个人的aim Output 你要求最后死亡数目的最小和最大可能 Sample Input 8 2 3 2 2 6 7 8 5 Sample Output 3 5 题解:最大:首先入度为0的点一定不会死:另外,

BZOJ 1124: [POI2008]枪战Maf

1124: [POI2008]枪战Maf Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 617  Solved: 236[Submit][Status][Discuss] Description 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺序开枪,且任意时刻只有一个人开枪.因此,对于不同的开枪顺序,最后死的人也不同. Input 输入n人数<1000000 每个人的aim Output 你要求最

[POI2008]枪战Maf题解

问题 C: [POI2008]枪战Maf 时间限制: 1 Sec  内存限制: 256 MB 题目描述 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺序开枪,且任意时刻只有一个人开枪.因此,对于不同的开枪顺序,最后死的人也不同. 输入 输入n人数<1000000 每个人的aim 输出 你要求最后死亡数目的最小和最大可能 样例输入 8 2 3 2 2 6 7 8 5 样例输出 3 5 本次考试最后一个题,被老师评论称难炸了--然而貌似不那么难,但你

BZOJ 1124 POI2008 枪战Maf 贪心

题目大意:给定n个神枪手,每个神枪手瞄准一个人,以一定顺序开枪,问最少和最多死多少人 首先考虑最多 对于每个联通块: 如果这个连通块只有一个人,那么这个人自杀,死亡人数为1 如果这个连通块是一个环,那么可以活下来一个人,死亡人数为size?1 否则除了叶节点之外其他人都可以死,死亡人数为size?cnt叶节点 接下来考虑最少 首先叶节点一定不能死 首先把叶节点加入队列,然后每取出一个点时,击杀他瞄准的人,然后如果他瞄准的人瞄准的人此时成为了一个叶节点,那么把这个人加入队列 最后会剩下一些环,一个

BZOJ 1124[POI2008]枪战

题面: 1124: [POI2008]枪战Maf Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 659  Solved: 259[Submit][Status][Discuss] Description 有n个人,每个人手里有一把手枪.一开始所有人都选定一个人瞄准(有可能瞄准自己).然后他们按某个顺序开枪,且任意时刻只有一个人开枪.因此,对于不同的开枪顺序,最后死的人也不同. Input 输入n人数<1000000 每个人的aim Output

CodeForces 706D Vasiliy&#39;s Multiset (字典树查询+贪心)

题意:最开始的时候有一个集合,集合里面只有一个元素0,现在有q次操作,操作分为3种: + x: 表示向集合中添加一个元素x - x:表示删除集合中值为x的一个元素 ? x:表示查询集合中与x异或的最大值为多少 析:这是一个字典树的应用,不过确实没看出来....主要思想是这样,先用10进制数,转成二进制数,记下每个结点的0,1的个数,这样增加和删除,就是对01的删除, 剩下的就是查询,那么尽量让0和1XOR是最大的,所以,对于给定数,我们要去尽量他的XOR数,如果找到就加上,找不到,就找下一个.这

POJ 3253 Fence Repair 类似哈夫曼树的贪心思想

Fence Repair Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 24550   Accepted: 7878 Description Farmer John wants to repair a small length of the fence around the pasture. He measures the fence and finds that he needs N (1 ≤ N ≤ 20,000)

bzoj 1907: 树的路径覆盖【贪心+树形dp】

我是在在做网络流最小路径覆盖的时候找到这道题的 然后发现是个贪心+树形dp \( f[i] \)表示在\( i \)为根的子树中最少有几条链,\( v[i] \) 表示在\( i \)为根的子树中\( i \) 是( 0)否(1)为一条链的端点 然后贪心转移即可(有链端点则连起来) #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=10005; i