CodeForces CF #503 Div.2

今天继续讲述我的掉分之路=。=

A. New Building for SIS

给你一个n栋楼,每栋楼高度为h。在对任意x满足a<=x<=b,有在x楼有楼梯,连接相邻的两个楼。然后是k组询问,回答两个坐标之间的最短路径的长度。

样例

设两个坐标为(x1,y1),(x2,y2)。这题有点类似于求曼哈顿距离,所以我们把横坐标和纵坐标分开来看。

那么对于横坐标而言,距离必定是fabs(x1-x2)。而对于纵坐标而言,如果y1>b&&yw>b,那么必须要绕到b层才可以通行,代价是(y1-b+y2-b),对y1

int main()
{
    long long n,h,a,b,k;
    cin>>n>>h>>a>>b>>k;
    for(long long i=0;i<k;i++)
    {
        long long x1,y1,x2,y2;
        cin>>x1>>y1>>x2>>y2;
        long long ans=fabs(x2-x1);
        if(x1==x2)ans=fabs(y1-y2);
        else if(y1>b&&y2>b)ans+=y1-b+y2-b;
        else if(y1<a&&y2<a)ans+=a-y1+a-y2;
        else ans+=fabs(y2-y1);
        cout<<ans<<endl;
    }
    return 0;
 } 

B. Badge

给你一张有向图,每个点只要一个出度,问从某个点出发,第一次访问到一个被访问过的点是什么。我一开始打了个tarjan来找环,然后发现直接模拟就好了。。。

比上一题简单…我觉得这次题目出的实在太懊糟了。

int main()
{
    bool vis[1005];
    int nx[1005];
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)cin>>nx[i];
    for(int i=1;i<=n;i++)
    {
        memset(vis,0,sizeof(vis));
        int cur=i;
        while(vis[cur]==0)
        {
            vis[cur]=1;
            cur=nx[cur];
        }
        cout<<cur<<" ";
    }

}

C. Political Metaphor

啊不好意思打错了,是C. Elections

第三题是说,有有n个政党,m个选民。每个选民用一定的BTC就可以收买。问你最少画多少钱能保证当选。

这个题目啊,exciting。我们会发现这题正面是很难做的,因为当你买的时候,改变的不止是自己的选票,还有没买走的那个政党的选票,这样一个动态的环境你比较难处理。但是我们又很想用贪心来做这个题,所以我们至少要让其中的某些变量定下来,那我们换一下思路,从反面来处理。

我们是政党1,我们穷举政党1可能的最终选票数。如果它小于政党一开始拥有的选票数,那么显然是不可能的,所以我们可以从一开始拥有的选票数开始(这是不搞黑幕的情况),穷举到拥有最多选票的政党的选票数+1(这是不动这个政党的选民,去拉其他政党的选民的票的情况)。

不妨设我们现在穷举到i,那么显然拥有最多选票的政党至多为i-1,那么我们在i-1的位置上切一刀,把高于i-1位置的选民都买走,设一开始拥有的选票数是pre,这里买了det个选民,那么接下来我们记录还需要买need个选民才能达到i个选民:

need=i-pre-det(1)

如果need<0那么说明买来这些已经超过了我要求的i,那么等于是不可能使自己的选民数恰好为i还能满足题意的,那么舍去。

如果need>=0那么我们还需要购买need个选民,我们在剩下的选民中任意选取(因为现在买谁的人都是一样的了,所以就可以贪心啦)need个费用最低的选民,记录为ans。再加上上面切那一刀花掉的钱,更新我们的最小答案minans。

当然闭着眼睛不优化的话复杂度是O(N3)那会T是显而易见的了。但是我做的时候没有想到了,T了就很难受,又做不出来,怎么办呢?我心态炸了(因为A题我wa了四次),就去搓炉石,突然看到一个4/4亡语召唤两个2/2亡语召唤两个1/1,我马上想到这不是logN吗,继而想到优先队列,继而优化了一下,继而ac了。

所以最终结果是O(N2logN)

#define INI(x) memset((x),0,sizeof(x))
#define MIN(x,y) (((x)<(y))?(x):(y))

struct voter{
    int b,p;
    voter(long long x,long long y):b(x),p(y){}
};
bool operator < (voter a,voter b){return a.b>b.b;};

long long n,m;
vector<long long> cost[3005];
vector<long long> sum[3005];
long long ansr=0xfffffffffffffff,ans=0;
bool check(int h)
{
    ans=0;
    int pick[3005];
    for(int i=2;i<=m;i++)pick[i]=0;
    int pre=cost[1].size();
    int det=0;
    for(int i=2;i<=m;i++)
    {
        int dt=cost[i].size()-h+1; 
        if(dt>0&&cost[i].size())det+=dt,ans+=sum[i][dt-1],pick[i]=dt;
    }
    if(det>h-pre)return 0;
    int need=h-pre-det;
    priority_queue<voter> nx;
    for(int i=2;i<=m;i++)if(pick[i]<cost[i].size())
    {
        voter ttt(cost[i][pick[i]],i);
        nx.push(ttt);
        pick[i]++;
    }
    while(need--)
    {
        if(nx.empty())
        {
            ans=0xfffffffffffffff;
            break;
        }
        voter topp=nx.top();
        nx.pop();
        ans+=topp.b;
        int ind=topp.p;
        if(pick[ind]<cost[ind].size())
        {
            voter ttt(cost[ind][pick[ind]],ind);
            nx.push(ttt);
            pick[ind]++;
        }
    }
    return 1;
}


int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int pp,cc;
        cin>>pp>>cc;
        cost[pp].push_back(cc);
    }
    for(int i=1;i<=m;i++)sort(cost[i].begin(),cost[i].end());
    int maxlen=-1; 
    for(int i=1;i<=m;i++)
    {
        if(!cost[i].empty())sum[i].push_back(cost[i][0]);
        for(int j=1;j<cost[i].size();j++)sum[i].push_back(sum[i][j-1]+cost[i][j]);
        int mm=cost[i].size();
        if(mm>maxlen)maxlen=mm;
    }
    int l=0;
    if(!cost[1].empty())l=cost[1].size();
    int r=maxlen+1;
    for(int i=l;i<=r;++i)
    {
        if(check(i))ansr=MIN(ansr,ans);
    }
    cout<<ansr<<endl;
    return 0;
}

D.The hat

交互题,不会

E.Sergey’s problem

http://codeforces.com/contest/1019/problem/C

直觉告诉我是dp做完来补

嗯对,我早就说了,这是一个O(n+m)的构造题。

我写一下伪代码,写完就懂了。

首先在外面保存一个pick[i]数组,表示选择点i没有。然后保存一个vis[i]数组,保存i被访问了几次。最后保存一个pnt,表示已经选到了第pnt的元素。pnt初始值为1。ans保存目前已经选取了几个点。

  • 伪代码

dfs()
①从当前的pnt开始找到下一个没有被访问过的元素
②如果pnt已经大于n,那么return
③pick[pnt]=true;vis[pnt]++;
④将每一个pnt点指向的点的访问数+1
⑤将pnt保存进一个临时变量save,因为进入下一层dfs以后pnt就会变化了
⑥dfs();
⑦若此时save点(也就是之前的pnt点)被访问了超过一次,那么pick[save]=false,vis[save]–,且将每一个save点指向的点访问数-1
⑧return;

  • 真代码
void dfs()
{
    while(pnt<=n&&vis[pnt])pnt++;//找到第一个未选取,未访问的点pnt
    if(pnt>n)return;//全部选取或访问过了,返回
    vis[pnt]++;//访问次数+1
    pick[pnt]=1;ans++;//pnt被选取
    for(int i=tail[pnt];i;i=e[i].next)vis[e[i].to]++;//可达点访问次数+1
    int point_save=pnt;//保存这个点的位置,防止在dfs中丢失 
    dfs();//去寻找下一个点
    if(vis[point_save]!=1)//若还被其他点访问过了,那么这个点就不取
    {
        pick[point_save]=0;ans--;
        for(int i=tail[point_save];i;i=e[i].next)vis[e[i].to]--; 
     } 
    return;
}
  • 证明

(这个证明相当玄学,我自己都觉得不靠谱)

注:【】中的内容表示在【】情况会发生

首先对于入度为0的点,一定是会被选中的。因为它不会被其他点访问到,所以vis始终为0,直到被选中。

其次,对于入度不为0的任何一个点,假设算法没有步骤⑦,即没有弹出的步骤,那么只有3种可能:
(1)有一个被选中的点指向它
(2)本身被选中且不被其他被选中的点指向
(3)本身被选中且有一个被选中的点指向它

当符合情况(1)时,那么这个点必定是合法的,并且有一个被选中的点指向它
当符合情况(2)时,这个点也必定是合法的(如果它指向一个被选中的点,那么这个点将在下面的步骤中被删除,所以我们将其视作合法的点)
当符合情况(3)时,那么这个点不合法

那么此时,我们需要将被这个点访问的且被选中的点踢出去,来保持我们v的合法性。

我们考虑一个被选中的点v,然后取出它指向的一个被选中的点u,然后将u踢出去。

将u踢出去以后,那么对于任何一个u指向的点w,如果w之前是情况(1),那么将变为情况(1)【当有另一个被选中的点指向w时】或情况:
(4)有一个被选中的点两步可以抵达它【没有另一个被选中的点指向w时】

如果w之前是情况(2),那么将变为情况(2)【当有另一个被选中的点指向w时】或情况(1)【没有另一个被选中的点指向w时】

如果w之前是情况(3),那么将变为情况(3)【当有另一个被选中的点指向w时】或情况(2)【没有另一个被选中的点指向w时】

我们可以看到,在进行完操作之后,所有被操作波及的点的最终状态都是(1)(2)或(4),满足题意。我们这样可以说明,在去除所有的状态(3)的点之后,余下的每个点都将处于合法的状态上,所以算法的正确性得证。

  • 思路

讲一下思路(其实我是没什么思路的)。

首先我们看到一个1e6的n,一个1e6的m,马上想到复杂度最大不能超过NlogN。然而这个题看着就像是O(n)的题目,所以不难想到dp,所以不难想到是一个构造算法,关键是这个怎么构造出来的。作为一个偷窥了dalao的标程以后才做出来的蒟蒻我实在不好意思吹嘘我是怎么想到这么构造的,但是我觉得这种两面都要顾及的题目,可以考虑先解决一面,另一面考玄学的数学证明来解决(雾)。

  • 复杂度

最后研究一下复杂度。
首先,pnt从1~n走一遍,不会回溯,所以复杂度是O(n)
其次,对于每条边,最多被访问两次,所以复杂度是O(m)
综上,复杂度是O(m+n),可以在线性时间内解决。

#define INI(x) memset(x,0,sizeof(x))
const int MAXN=1e6+3;
int n,m,pnt=1,ans=0;

bool pick[MAXN];
int vis[MAXN];

struct E{int to,next;}e[MAXN];
int tail[MAXN];
int cnt=0;
void edge_add(int f,int t){e[++cnt].to=t;e[cnt].next=tail[f];tail[f]=cnt;}

void read(int &x)
{
    x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return;
}

void INITIATE()
{
    INI(pick);
    INI(vis);
    INI(tail);
    read(n);
    read(m);
    int f,t;
    for(int i=0;i<m;i++)read(f),read(t),edge_add(f,t);
    return;
}

void dfs()
{
    while(pnt<=n&&vis[pnt])pnt++;//找到第一个未选取,未访问的点pnt
    if(pnt>n)return;//全部选取或访问过了,返回
    vis[pnt]++;//访问次数+1
    pick[pnt]=1;ans++;//pnt被选取
    for(int i=tail[pnt];i;i=e[i].next)vis[e[i].to]++;//可达点访问次数+1
    int point_save=pnt;//保存这个点的位置,防止在dfs中丢失 
    dfs();//去寻找下一个点
    if(vis[point_save]!=1)//若还被其他点访问过了,那么这个点就不取
    {
        pick[point_save]=0;ans--;
        for(int i=tail[point_save];i;i=e[i].next)vis[e[i].to]--; 
     } 
    return;
}

int main()
{
    INITIATE();
    dfs();
    cout<<ans<<endl;
    for(int i=1;i<=n;i++)if(pick[i])cout<<i<<" "; 
 } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值