2017-10-2 清北刷题冲刺班a.m

一道图论神题

(god)

Time Limit:1000ms   Memory Limit:128MB

 

题目描述

LYK有一张无向图G={V,E},这张无向图有n个点m条边组成。并且这是一张带权图,只有点权。

LYK想把这个图删干净,它的方法是这样的。每次选择一个点,将它删掉,但删这个点是需要代价的。假设与这个点相连的还没被删掉的点是u1,u2,…,uk。LYK将会增加a[u1],a[u2],…,a[uk]的疲劳值。

它想将所有点都删掉,并且删完后自己的疲劳值之和最小。你能帮帮它吗?

 

输入格式(god.in)

    第一行两个数n,m表示一张n个点m条边的图。

    第二行n个数ai表示点权。

    接下来m行每行三个数u,v,表示有一条连接u,v的边。数据保证任意两个点之间最多一条边相连,并且不存在自环。

 

输出格式(god.out)

你需要输出这个最小疲劳值是多少。

 

输入样例

4 3

10 20 30 40

1 4

1 2

2 3

 

输出样例

40

 

样例解释

一个合理的方法是先删4号点,此时有10点疲劳值。接下来删3号点,获得20点疲劳值,再删2号点,获得10点疲劳值,最后删1号点,没有疲劳值。总计40点疲劳值。

 

对于30%的数据n<=10。

对于60%的数据n,m<=1000。

对于100%的数据1<=n,m,ai<=100000

/*
    贪心
    每次选择权值最大的点删除即可 
*/
#include<iostream>
#include<cstdio>
#include<queue>
using namespace std;
#define maxn 100010
int n,m,head[maxn],w[maxn];
struct node{
    int to,pre,v;
}e[maxn*2];
struct Node{
    int id,val;
    bool operator < (const Node b)const{
        return val<b.val;
    }
};
priority_queue<Node>q;
int qread(){
    int i=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch<='9'&&ch>='0'){i=i*10+ch-'0';ch=getchar();}
    return i;
}
void Insert(int from,int to,int v,int num){
    e[num].to=to;
    e[num].pre=head[from];
    e[num].v=v;
    head[from]=num;
}
int main(){
    //freopen("Cola.txt","r",stdin);
    freopen("god.in","r",stdin);freopen("god.out","w",stdout);
    n=qread();m=qread();
    Node now;
    for(int i=1;i<=n;i++){
        w[i]=qread();
        now.id=i;now.val=w[i];
        q.push(now);
    }
    int x,y;
    for(int i=1;i<=m;i++){
        x=qread();y=qread();
        Insert(x,y,w[y],i);Insert(y,x,w[x],i+m);
    }
    long long ans=0;
    while(!q.empty()){
        now=q.top();q.pop();
        int cur=now.id;
        for(int i=head[cur];i;i=e[i].pre){
            ans+=e[i].v;
            if(i<=m)e[i].v=0,e[i+m].v=0;
            if(i>m)e[i].v=0,e[i-m].v=0;
        }
    }
    cout<<ans;
    fclose(stdin);fclose(stdout);
    return 0;
}
100分 贪心

 

位运算2

(bit)

Time Limit:1000ms   Memory Limit:128MB

 

题目描述

LYK拥有一个十进制的数N。它赋予了N一个新的意义:不考虑N的符号,将N每一位都拆开来后再加起来就是N所拥有的价值。例如数字123拥有6的价值,数字999拥有27的价值,数字-233拥有8的价值。

假设数字N的价值是K,LYK想找到一个价值是K+1的数字,当然这个答案实在太多了,LYK想使得这个价值为K+1的数字尽可能大,并且需要保证这个数字小于N。

 

输入格式(bit.in)

    一个整数N。

 

输出格式(bit.out)

一个数表示答案。你需要输出一个整数,且这个数不包含前导0。

 

输入样例1

199

 

输出样例1

-299

 

输入样例2

1520

 

输出样例2

1512

 

对于20%的数据|N|<=10

对于40%的数据|N|<=100

对于60%的数据|N|<=10^9

对于80%的数据|N|<=10^1000

对于100%的数据|N|<=10^100000。

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 100010
using namespace std;
int bit[maxn],len,f[maxn],w;
char s[maxn];
bool flag=0;
void dfs(int pos,bool limit,int sum){
    if(flag)return;
    if(sum>w)return;
    if(pos==len+1){
        if(sum==w){
            int start=1;
            while(f[start]==0)start++;
            for(int i=start;i<=len;i++)printf("%d",f[i]);
            flag=1;
        }
        return;
    }
    if(limit){
        for(int i=bit[pos];i>=0;i--){
            f[pos]=i;
            dfs(pos+1,limit&&(i==bit[pos]),sum+i);
        }
    }
    else{
        for(int i=9;i>=0;i--){
            f[pos]=i;
            dfs(pos+1,0,sum+i);
        }
    }
}
int main(){
    //freopen("Cola.txt","r",stdin);
    freopen("bit.in","r",stdin);freopen("bit.out","w",stdout);
    scanf("%s",s+1);
    len=strlen(s+1);
    if(s[1]<='9'&&s[1]>='0')for(int i=1;i<=len;i++)bit[i]=s[i]-'0';
    else {
        for(int i=2;i<=len;i++)bit[i]=s[i]-'0';
        bit[1]=-1;
    }
    if(bit[1]!=-1){//n是正数 
        if(bit[len]<=7&&bit[len-1]>0){
            bit[len]+=2;
            bit[len-1]--;
            for(int i=1;i<=len;i++)printf("%d",bit[i]);
            return 0;
        }
        for(int i=1;i<=len;i++)w+=bit[i];
        w++;
        dfs(1,1,0);
        if(flag)return 0;
        int cnt=0;
        while(w){
            if(w>=9){
                f[++cnt]=9;
                w-=9;
            }
            else f[++cnt]=w,w=0;
        }
        printf("-");
        for(int i=cnt;i>=1;i--)printf("%d",f[i]);
    }
    if(bit[1]==-1){//n是负数 
        int now=len;
        while(now>2&&bit[now]==9)now--;
        if(bit[now]!=9){
            bit[now]++;
            printf("-");
            for(int i=2;i<=len;i++)printf("%d",bit[i]);
            return 0;
        }
        else{
            printf("-1");
            for(int i=2;i<=len;i++)printf("%d",bit[i]);
            return 0;
        }
    }
}
80分 暴力
/*
    全程特判:
    1.最后一位<8,倒数第二位>0,最后一位+2,倒数第二位-1
    2.6368899->6359999
    3.4566544099->4566543992
        从后往前,统计9-a[i]的和,直到>=2。
        再往前,找第一个>0的数,把这个数-1,  再整理下后面的数(一开始全是9...,尽可能大)
*/
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
char s[100005];
int len,sum,i,j,FLAG;
int main()
{
    freopen("bit.in","r",stdin);
    freopen("bit.out","w",stdout);
        scanf("%s",s); FLAG=0;
        if (s[0]!='-')
        {
        len=strlen(s); sum=0;
        for (i=len-1; i>=0; i--)
        {
            if (s[i]>='1' && sum>=2)
            {
                s[i]=char(s[i]-1);
                sum=2;
                for (j=i+1; j<len; j++)
                {
                    while (sum && s[j]<'9')
                    {
                        sum--;
                        s[j]=char(s[j]+1);
                    }
                }
                int SS=0;
                for (j=i+1; j<len; j++) SS+=s[j]-'0';
                for (j=i+1; j<len; j++)
                {
                    if (SS>=9) {s[j]='9'; SS-=9;} else {s[j]=char(SS+'0'); SS=0;}
                }
                FLAG=1;
                break;
            }
            sum+='9'-s[i];
        }
        if (FLAG)
        {
            for (i=0; i<len; i++) if (s[i]!='0') break;
            for (j=min(len-1,i); j<len; j++) cout<<s[j]; cout<<endl;
            return 0;
        }
        cout<<'-';
        for (i=len-1; i>=0; i--)
        {
            if (s[i]<'9')
            {
                s[i]=char(s[i]+1);
                FLAG=1;
                break;
            }
        }
        if (FLAG)
        {
            for (i=0; i<len; i++) if (s[i]!='0') break;
            for (j=min(len-1,i); j<len; j++) cout<<s[j]; cout<<endl;
            return 0;
        }
        cout<<1;
            for (i=0; i<len; i++) if (s[i]!='0') break;
            for (j=min(len-1,i); j<len; j++) cout<<s[j]; cout<<endl;
        return 0;
        }

        FLAG=0;
        cout<<'-';
        len=strlen(s);
        for (i=len-1; i>=1; i--)
        {
            if (s[i]<'9')
            {
                s[i]=char(s[i]+1);
                FLAG=1;
                break;
            }
        }
        if (FLAG)
        {
            for (i=1; i<len; i++) if (s[i]!='0') break;
            for (j=min(len-1,i); j<len; j++) cout<<s[j]; cout<<endl;
            return 0;
        }
        cout<<1;
            for (i=1; i<len; i++) if (s[i]!='0') break;
            for (j=min(len-1,i); j<len; j++) cout<<s[j]; cout<<endl;
    return 0;
}
100分

 

逆序对

(pair)

Time Limit:1000ms   Memory Limit:128MB

 

题目描述

LYK最近在研究逆序对。

这个问题是这样的。

一开始LYK有一个2^n长度的数组ai。

LYK有Q次操作,每次操作都有一个参数k。表示每连续2^k长度作为一个小组。假设n=4,k=2,则a[1],a[2],a[3],a[4]为一个小组,a[5],a[6],a[7],a[8]为一个小组,a[9],a[10],a[11],a[12]为一个小组,a[13],a[14],a[15],a[16]也为一个小组。

然后LYK对于每个小组都翻转,也就是说原数组会变成a[4],a[3],a[2],a[1],a[8],a[7],a[6],a[5],a[12],a[11],a[10],a[9],a[16],a[15],a[14],a[13]。之后它想求出这2^n个数的逆序对是多少。

因此你需要输出对于每次操作,操作完后这2^n个数的逆序对有多少对。

两个数ai,aj被称为逆序对当且仅当i<j且ai>aj。

 

输入格式(pair.in)

    第一行一个数n。

接下来一行2^n个数ai表示一开始的数组。

接下来一行一个数Q,表示操作的次数。

接下来一行Q个数,表示每次操作的参数k。

 

输出格式(pair.out)

Q行,表示每次操作后的答案。

 

输入样例

2

2 1 4 3

4

1 2 0 2

 

输出样例

0

6

6

0

 

样例解释

第一次操作,{2,1,4,3}->{1,2,3,4}

第二次操作,{1,2,3,4}->{4,3,2,1}

第三次操作,{4,3,2,1}->{4,3,2,1}       

第四次操作,{4,3,2,1}->{1,2,3,4}

 

对于30%的数据n<=10,Q<=10。

对于50%的数据n<=10,Q<=1000。

对于80%的数据n<=10,Q<=200000。

对于100%的数据n<=17,Q<=200000,1<=ai<=2^n。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define maxn 131073
int n,a[2][maxn],Q,tr[maxn];
long long ans;
int qread(){
    int i=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch<='9'&&ch>='0'){i=i*10+ch-'0';ch=getchar();}
    return i;
}
long long query(int pos){
    long long res=0;
    while(pos){
        res+=tr[pos];
        pos-=pos&(-pos);
    }
    return res;
}
void add(int pos,int v){
    while(pos<=(1<<n)){
        tr[pos]++;
        pos+=pos&(-pos);
    }
}
int main(){
    //freopen("Cola.txt","r",stdin);
    //freopen("pair.in","r",stdin);freopen("pair.out","w",stdout);
    n=qread();
    for(int i=1;i<=(1<<n);i++)a[0][i]=qread();
    Q=qread();
    int x;
    for(int i=1;i<=Q;i++){
        memset(tr,0,sizeof(tr));
        x=qread();x=(1<<x);
        for(int j=1;j<=(1<<n);j+=x)//区间反转 
            for(int cnt=1,k=j,l=j+x-1;cnt<=x;cnt++,k++,l--)
                a[i&1][k]=a[(i-1)&1][l];
        ans=0;
        for(int j=1;j<=(1<<n);j++){
            ans+=j-1-query(a[i&1][j]);
            add(a[i&1][j],1);
        }
        cout<<ans<<endl;
    }
    fclose(stdin);fclose(stdout);
    return 0;
}
树状数组(考场代码)
/*
    12345678
    
    [1,4][5,8]
    56781234
    
    [1,2][3,4]   [5,6][7,8]
    78563412
    
    [1,1][2,2]  [3,3][4,4]  [5,5][6,6]  [7,7][8,8]
    87654321
    
    k=4 -> 
    
    把一个对k的翻转操作  转变成对k,k-1,k-2,...,1的平移操作
    平移操作对答案有什么影响
    对3进行平移,[1,4][5,8] -> [5,8][1,4]  对于所有8个数为一组,答案=逆序对个数的最大值-当前答案
    
    1,2,2,3   当前逆序对(只考虑i属于[1,2],j属于[3,4]的逆序对)=0  逆序对个数的最大值:2*2-相同对数 = 3
    
    2,3,1,2    答案 = 3-0 = 3
    
    记录一下  所有连续2^k个数的当前逆序对个数,以及逆序对个数的最大值。
    每次操作的时候,转变成平移操作
    
    2^4  16
    [x,y] 令 mid=(x+y)/2  对于所有i属于[x,mid]  j属于[mid+1,y]  这样逆序对的个数
    f[1]=[1,2]+[3,4]+[5,6]+...+[15,16]
    f[2]=[1,4]+[5,8]+[9,12]+[13,16]
    f[3]=[1,8]+[9,16]
    f[4]=[1,16]
    
    g来表示最大的逆序对个数 (总共的对数-相同的对数)
    
    对于每次对k进行操作
    n^2*2^n
    while (Q--)
    {
        cin>>k;
        for (int i=1; i<=k; i++) f[i]=g[i]-f[i];
        ans=0;
        for (int i=1; i<=n; i++) ans+=f[i];
        cout<<ans<<endl;
    }
    [1,4]  [5,8]
    
    [5,8]  [1,4]

*/
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <string>
#include <cstring>
#include <map>
#include <vector>
using namespace std;
const int N=(1<<17)+5;
long long st[N][20],ST[N][20];
int p[N],i,sum,o,a[N],b[N],n,T,PP,now,j,A,RR[N];
void gb(int l,int r)
{
    if (l==r) return;
    int mid=(l+r)/2;
    gb(l,mid); gb(mid+1,r);
    int i=l,j=mid+1,o=l;
    for (i=l; i<=r; i++) b[i]=a[i];
    for (i=r; i>=mid; i--) {RR[i]=i; if (i!=r && b[i+1]==b[i]) RR[i]=RR[i+1];}
    i=l; j=mid+1;
    while (i<=mid && j<=r)
    {
        if (b[i]<=b[j]) {a[o++]=b[i];if (b[i]==b[j]) ST[l][p[r-l+1]]+=RR[j]-j+1; i++;} else
        {
            a[o++]=b[j]; st[l][p[r-l+1]]+=mid-i+1;

            j++;
        }
    }
    if (i<=mid) for (j=i; j<=mid; j++) a[o++]=b[j]; else
    if (j<=r) for (i=j; i<=r; i++) a[o++]=b[i];
}
long long t[105],TT[105];
int main()
{
    freopen("pair.in","r",stdin);
    freopen("pair.out","w",stdout);
    scanf("%d",&n);
    sum=(1<<n);
    for (i=1; i<=sum; i++) scanf("%d",&a[i]);
    for (i=1; i<=17; i++) p[1<<i]=i;
    gb(1,sum);
    scanf("%d",&T);
    for (i=1; i<=n; i++)
      for (j=1; j<=(1<<n); j+=(1<<i))
      {
          t[i]+=st[j][i];
          TT[i]+=ST[j][i];
      }
    long long ans=0;
    while (T--)
    {
        int Q;
        scanf("%d",&Q);
        for (i=1; i<=Q; i++) t[i]=1ll*(1<<i-1)*(1<<i-1)*(1<<n-i)-TT[i]-t[i];
        for (i=1; i<=n; i++) ans+=t[i];
        printf("%I64d\n",ans); ans=0;
    }
    return 0;
}
100分 区间反转->区间平移

 

posted @ 2017-10-02 17:05  Echo宝贝儿  阅读(207)  评论(0编辑  收藏  举报