首页 > 代码库 > 8.8联考题解

8.8联考题解

今天的T1让我怀疑我是不是在做奥赛题……这考的是什么知识点啊这个,会不会用绝对值函数?

 

Evensgn 的债务

时间限制: 1 Sec  内存限制: 128 MB

题目描述

Evensgn 有一群好朋友,他们经常互相借钱。假如说有三个好朋友A,B,C。A 欠 B 20 元,B 欠 C 20 元,总债务规模为 20+20=40 元。Evensgn 是个追求简约的人,他觉得这样的债务太繁杂了。他认为,上面的债务可以完全等价为 A 欠C20 元,B 既不欠别人,别人也不欠他。这样总债务规模就压缩到了 20 元。现在给定 n 个人和 m 条债务关系。Evensgn 想找到一种新的债务方案,使得每个人欠钱的总数不变,或被欠钱的总数不变(但是对象可以发生变化),并且使得总债务规模最小。

 

输入

输入文件第一行两个数字 n; m,含义如题目所述。

接下来 m 行,每行三个数字 ai; bi; ci,表示 ai 欠 bi 的钱数为 ci。

注意,数据中关于某两个人 A 和 B 的债务信息可能出现多次,将其累加即可。

如”A 欠 B 20 元”、”A 欠 B 30 元”、”B 欠 A 10 元”,其等价为”A 欠 B 40 元”。

 

输出

输出文件共一行,输出最小的总债务规模。

 

样例输入输出

样例输入 1
5 3
1 2 10
2 3 1
2 4 1
样例输入 2
4 3
1 2 1
2 3 1
3 1 1

样例输出

样例输出 1
10
样例输出 2
0

数据范围

对于 30% 的数据,1 ≤ n ≤ 10,1 ≤ m ≤ 10。

对于 60% 的数据,1 ≤ n ≤ 100, 1 ≤ m ≤ 104。

对于 80% 的数据,1 ≤ n ≤ 104,1 ≤ m ≤ 104。

对于 100% 的数据,1 ≤ n ≤ 106,1 ≤ m ≤ 106。

对于所有的数据,保证 1 ≤ ai; bi ≤ n; 0 < ci ≤ 100。

 

题解

      不想写题解……刚开始觉得,这是绝对值之和/2吧?弄了个环的例子绕一绕,好像还真没什么问题……后来闲得慌自己证明,发现用负数表示borrow,正数表示lend,正负数一消取绝对值正好是债务规模,似乎挺有道理?交题之前又有些犹豫,这题是考的什么算法啊?还是交了,居然A了……A掉这道题,你需要掌握:循环语句、开数组、输入输出、调库、绝对值函数(不会用cmath库用if语句判断取相反数也行),可以给下一届高一当第一次考试题了;或许文化课一调的语句填空也行。昨天的第一题,虽然是原题好歹是题,还用了MP算法,但是这个,完全不懂意义何在。

技术分享
include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
using namespace std;
const int sj=1000010;
int a[sj]={0},n,m,a1,a2,a3,ans=0;
int r()
{
    int jg=0,jk;
    jk=getchar()-0;
    if(jk<=9&&jk>0) jg=jk;
    jk=getchar()-0;
    while(jk>=0&&jk<=9)
    {
       jg=jg*10+jk;
       jk=getchar()-0;
    }
    return jg;
}
int main()
{
    //freopen("t1.txt","r",stdin);
    n=r();
    m=r();
    for(int i=1;i<=m;i++) 
    {
       a1=r();
       a2=r();
       a3=r();
       a[a1]-=a3;
       a[a2]+=a3;
    }
    for(int i=1;i<=n;i++)  ans+=abs(a[i]);
    ans/=2;
    printf("%d",ans);
    //while(1);
    return 0;
}
debt

 

Number

时间限制: 1 Sec  内存限制: 256 MB

题目描述

一个排列,求出了 a 数组,其中 ai 表示第 i 个数左边有多少个数比它小

计算出原来的排列

输入

第一行输入 n 接下来 n - 1 个整数 ai,下标从 2 开始

输出

输出 n 个整数表示原排列

样例输入

5
1
2
1
0

样例输出

2
4
5
3
1

提示

对于 20% 的数据满足:1 ≤ n ≤ 10

对于 50% 的数据满足:1 ≤ n ≤ 1000

对于 100% 的数据满足,1 ≤ n ≤ 100000

保证解存在

 

题解

       这道题还是比较有意思的。刚开始啥都想不出来,先打了个dfs,又弄了个出数据的程序(走向对拍?)。发现dfs只能跑20分,有点难过。第三题看起来很不友好,第二题还拿不了分这是要完啊……然后写写画画,猛然发现这个最后一个好像是确定的,那倒数第二个呢,倒数第三个呢……这不就想出来了吗,记录一下每个数的temp(左边可能有几个比它小的),选出来一个就把比它大的都--,每次查哪一个数正好符合要求就行了。需要快速查询,一开始想的是链表。打了个(n^2+2^n)/2的链表版本,出不了样例,又转去打n^2的,发现好像先选大的和先选小的还不一样。感性地论证一下,较小数的temp更不容易变小,所以尽量先选小的。心怀疑虑地把链表版本改对,和1000的数据拍了一下发现能过,也就放心了。其实这个效率跑个10000也是绰绰有余的,但是出题人说给50就给50多一点分都没有QAQ。第三题想不出来再回来看,要把n^2降下来,查询得放在树上,恍然大悟——原来是个平衡树题啊。但是没想到可以用有旋Treap查询和删除,被50分版本限制住了思路一直想着Splay或者无旋Treap的区间操作,Splay极容易打挂无旋Treap没打过区间build函数不会写,还是算了(早知道有旋Treap也可以做就打正解了嘛)。下午改题欢乐地拿有旋Treap轻松水过,delete函数写错一个字母又查了半天错。

技术分享
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
const int sj=100010;
int n,a[sj],ans[sj],size,root;
struct Treap
{
     int v,l,r,key,size;
}t[sj];
void update(int x)
{
     t[x].size=t[t[x].l].size+t[t[x].r].size+1;
}
void rturn(int &x)
{
     int tt=t[x].l;
     t[x].l=t[tt].r;
     t[tt].r=x;
     t[tt].size=t[x].size;
     update(x);
     x=tt;
}
void lturn(int &x)
{
     int tt=t[x].r;
     t[x].r=t[tt].l;
     t[tt].l=x;
     t[tt].size=t[x].size;
     update(x);
     x=tt;
}
void cr(int &x,int y)
{
     if(x==0)
     {
        size++;
        x=size;
        t[x].size=1;
        t[x].v=y;
        t[x].key=rand();
        return;
     }
     t[x].size++;
     if(y>t[x].v)
     {
         cr(t[x].r,y);
         if(t[t[x].r].key<t[x].key)  lturn(x);
     }
     else
     {
         cr(t[x].l,y);
         if(t[t[x].l].key<t[x].key)  rturn(x);
     }
}
int query(int x,int y)
{
    if(x==0) return 0;
    if(t[t[x].l].size>=y) return query(t[x].l,y);
    if(t[t[x].l].size+1<y) return query(t[x].r,y-t[t[x].l].size-1);
    return t[x].v;
}
void sc(int &x,int y)
{
     if(x==0) return;
     if(t[x].v==y)
     {
       if(t[x].l*t[x].r==0) x=t[x].l+t[x].r;
       else if(t[t[x].l].key<t[t[x].r].key)
         rturn(x),sc(x,y);
       else  
         lturn(x),sc(x,y);
     }
     else if(y>t[x].v)
        t[x].size--,sc(t[x].r,y);
     else    t[x].size--,sc(t[x].l,y);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)  cr(root,i);
    for(int i=2;i<=n;i++)  scanf("%d",&a[i]);
    ans[n]=a[n]+1;
    sc(root,ans[n]);
    for(int i=n-1;i>1;i--)
    {
       ans[i]=query(root,a[i]+1);
       sc(root,ans[i]); 
    }
    ans[1]=t[root].v;
    for(int i=1;i<=n;i++)
       printf("%d\n",ans[i]);
    return 0;
}
number

 

与非

时间限制: 2 Sec  内存限制: 256 MB

题目描述

作为一名新世纪共产主义的接班人,你认识到了资本主义的软弱性与妥协性,决定全面根除资本主义,跑步迈入共产主义。但是当你即将跨入共产主义大门的时候,遇到了万恶的资本家留下的与非电路封印,经过千辛万苦的研究,你终于把复杂的破解转变成了以下问题:

初始时你有一个空序列,之后有N个操作。

操作分为一下两种:

1 x:在序列末尾插入一个元素x(x=0或1)。

2 L R:定义nand[L,R]为序列第L个元素到第R个元素的与非和,询问nand[L,L]^nand[L,L+1]^nand[L,L+2]^......^nand[L,R]。

Nand就是先与,再取反

输入

从文件nand.in中读入数据。

输入第一行一个正整数N,表示操作个数。

接下来N行表示N个操作。

为了体现程序的在线性,记lastans为上一次操作二的回答,初始lastans=0,。对于操作1,你需要对x异或lastans。对于操作二,设现在序列中的元素个数为M,如果lastans=1,那么你需要作如下操作:L=M-L+1,R=M-R+1,swap(L,R)

输出

输出到nand.out中。

输出有多行。为对于每一个操作二的回答。

样例输入

6
1 1
1 1
1 0
2 1 2
2 1 3
2 2 3

样例输出

1
0
0

提示

【数据规模和约定】

数据点    N的规模    操作一的个数M1    操作二的个数M2

1            N<=1000         M1<=500               M2<=500

2            N<=1000         M1<=500               M2<=500

3           N<=200000      M1<=100000        M2<=100000

4           N<=200000      M1<=100000        M2<=100000

5          N<=1000000     M1<=900000        M2<=100000

6          N<=4000000     M1<=3900000      M2<=100000

7          N<=4000000     M1<=3900000      M2<=100000

8          N<=4000000     M1<=3900000     M2<=100000

9          N<=4000000     M1<=3900000     M2<=100000

10        N<=4000000     M1<=3900000     M2<=100000

 

对于所有数据,满足N<=4000000,M1<=3900000,M2<=100000,x={0,1},L<=R。

由于输入规模较大,请使用较快的读入方式以防读入超时。

 

题解

       %%%超哥。我承认我确实没读懂题,所以连20的暴力分都没拿到。连续两天T3爆零,很忧伤啊。超哥的思路完胜std!

         f[i]=!(f[i-1]&a[i])
         nand[i,j]=!(nand[i,j-1]&a[j])=!(……!(!(!(a[i]&a[i+1]))&a[i+2])……&a[j])
         f[j]=!(f[j-1]&a[j])=!(……!(!(!(f[i-1]&a[i])&a[i+1]))&a[i+2])……&a[j])
         唯一的差别在中间部分,真正的nand[i,j]是a[i]&a[i+1],而f[j]是(!(f[i-1]&a[i]))&a[i+1]
         a[i+1]=0时前后都是0,一定相等
         a[i+1]=1时若a[i]=0,前为0,后为1
                         若a[i]=1且f[i-1]=1,前为1,后为0
                         若a[i]=1但f[i-1]=0,前后还是相等的

         对于每一个询问的区间,从它有一位为0开始就可以套根据f数组计算出的前缀和sum了~至于前面的,暴力算吧。

         其实也是想了想前缀和区间转化之类的思路的,但是没想出来……改题的时候想着亦或有可减性(亦或两次变成1),本蒟蒻傻傻地打了sum[r]-sum[l-1]然后成功出了负数……最后批判这个题面一句:名字叫与非,题干里说先与再取反,虽然说大概能理解可是毕竟二进制还有个~运算叫取反啊……

技术分享
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int sj=3900005;
int r()
{
    int jg=0,jk;
    jk=getchar()-0;
    if(jk<=9&&jk>0) jg=jk;
    jk=getchar()-0;
    while(jk>=0&&jk<=9)
    {
       jg=jg*10+jk;
       jk=getchar()-0;
    }
    return jg;
}
int n,a1,a2,op,s[sj],lans,ge,t,jg,f[sj],sum[sj],na[sj];
int main()
{
    n=r();
    sum[0]=1;
    for(int l=1;l<=n;l++)
    {
        op=r();
        a1=r();
        if(op==1)
        {
           a1^=lans;
           ge++;
           s[ge]=a1;
           f[ge]=!(f[ge-1]&s[ge]);
           sum[ge]=sum[ge-1]^f[ge];
           if(ge==1)  f[1]=s[1],sum[1]=s[1];
           
        }
        if(op==2)
        {
           a2=r();
           if(lans==1)
           {
              a1=ge-a1+1;
              a2=ge-a2+1;
              t=a1;
              a1=a2;
              a2=t;
           }
           jg=na[a1]=s[a1];
           for(int j=a1+1;j<=a2;j++)
           {
             if(s[j]==1)
             {
               na[j]=!(na[j-1]&s[j]);
               jg^=na[j];
             }
             else
             {
                jg^=sum[a2];
                jg^=sum[j-1];
                break;
             }
           }
           lans=jg;
           printf("%d\n",jg);
        }
    }
    return 0;
}
nand

 

8.8联考题解