新疆大学ACM-ICPC程序设计竞赛五月月赛(同步赛)- (A,C,H,G)

链接:https://www.nowcoder.com/acm/contest/116#question

C勤奋的杨老师
题意:给出长为n的数组,让求的从左端点到某点的最长子序列长度加上从右端点到某点的最长子序列长度之和的最大值。

解析:从左到右两次求最长子序列就行,但是这里要用O(n*logn)的算法才不会超时。

算法原理来自:https://blog.csdn.net/George__Yu/article/details/75896330

新建一个low数组,low[i]表示长度为i的LIS结尾元素的最小值。对于一个上升子序列,显然其结尾元素越小,越有利于在后面接其他的元素,LIS也就越可能变得更长。因此,我们只需要维护low数组,对于每一个a[i],如果a[i] > low[当前最长的LIS长度],就把a[i]接到当前最长的LIS后面,即low[++当前最长的LIS长度]=a[i]。
那么,怎么维护low数组呢?
对于每一个a[i],如果a[i]能接到LIS后面,就接上去;否则,就用a[i]取更新low数组。具体方法是,在low数组中找到第一个大于等于a[i]的元素low[j],用a[i]去更新low[j]。如果从头到尾扫一遍low数组的话,时间复杂度仍是O(n^2)。我们注意到low数组内部一定是单调不降的,所有我们可以二分low数组,找出第一个大于等于a[i]的元素。二分一次low数组的时间复杂度的O(lgn),所以总的时间复杂度是O(nlogn)。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod =1000000007;
const int M = 500005;
int ans,n,cnt;
int a[500005],low[500005];
int lr[500005],rl[500005];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        int k=upper_bound(low,low+cnt,a[i])-low;
        low[k]=a[i];
        if(k==cnt) cnt++;
        lr[i]=cnt;
    }
    cnt=0;
    memset(low,0,sizeof(low));
    for(int i=n;i>=2;i--)
    {
        int k=upper_bound(low,low+cnt,a[i])-low;
        low[k]=a[i];
        if(k==cnt) cnt++;
        rl[i]=cnt;
        ans=max(ans,rl[i]+lr[i-1]);
    }
    cout<<ans<<endl;
    return 0;
}

ARed Rover
题意:给出由{N,S,E,W}组成的一个串,现在可以用单个字符M代替原串中某个重复出现的子串,原串可以表示为: 用单个字符M代替原串中某个重复出现的子串后的新串+M所代替的子串,问能替换原串的最短的串的长度

解析:看到字符串比较小,所以暴力枚举子串再用KMP来求值

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
string str,son;
int nex[10010];
void getNext()
{
    nex[0]=-1;
    int lenw=son.length();
    int temp=-1;//
    int j;
    for(j=0; j<lenw; )
    {
        if(temp==-1 || son[j]==son[temp])//比较当前位j与next[j] 的字符是否相等结束条件
        {
            j++;//下一位
            temp++;//temp跟上
            nex[j]=temp;//求得下一位的值
        }
        else
            temp=nex[temp];//找next[next[]],并查集思想
    }
}
int getKMP()
{
    int lenw=son.length();
    int lent=str.length();
    getNext();
    int ans=0;
    int k=0;//k表示 配对了k个字符    k最大是lent!!
    for(int i=0; i<lent; )
    {
        if(k==-1 || str[i]==son[k])
        {
            i++;
            k++;
            if(k==lenw)
            {
                ans++;
                k=0;   //加上这句话就可以 求不能重叠的
            }
        }
        else
            k=nex[k];
    }
    return ans;
}
int main()
{
    cin>>str;
    int len=str.length();
 
    int ans=len;
    for(int l=1;l<=len;l++)       //子串长度
    {
        for(int i=0;i<len-l+1;i++)//子串起点
        {
            son="";
            for(int j=0;j<l;j++) //建立子串
                son+=str[i+j];
            int tmp=getKMP();
            //cout<<son<<" "<<tmp<<endl;
            ans=min(ans,len-tmp*(int)son.length()+tmp+(int)son.length());
        }
    }
    cout<<ans<<endl;
    return 0;
}
HXOR

题意:现有0~N-1共N个城市,现在想把所有城市连起来,连接两个城市之间的花费就是两个城市编号异或的值

解析:其实是让从0-N-1之间做一个最小生成树,想到这点就好说了,我们把城市i加入最小生成树的最小花费就是lowbit(i),学过树状数组的应该还记得,这里的lowbit(i)就是对于数值i的二进制形式保留最低位的1及其后面的所有0的值,

原因是,一个数a要想异或另一个数b并使得异或值最小,在a==b时最小,但节点编号唯一,所以与a异或的数只能是只有最低位不相同其他位全部相同的(此时,这里其实是从0~N-1按顺序加入的)

代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
int main()
{
    while(cin>>n)
    {
        ll ans=0;
        for(int i=0;i<n;i++)
            ans+=(i&-i);
        cout<<ans<<endl;
    }
}

如下暴力法能过:

#include<bits/stdc++.h>
using namespace std;
int a[20005];
int main()
{
    for(int i=1;i<=20000;i++)
    {
        int minn=800000000;
        for(int j=0;j<i;j++)
        {
            minn=min(minn,j^i);
        }
        a[i]=a[i-1]+minn;
    }
    int n;
    while(cin>>n)
    {
        cout<<a[n-1]<<endl;
    }
}

Gchess

题意:有一个棋盘,laowang和xiaoren下棋,棋子只能往下或者往左或者往左下走(可以直走多个距离),棋子现位于(x,y),原点在左下角,xiaoren先走,问谁能先到最左下角即原点

解析:往下走i步就是y减去i,往左走i步就是x减去i,往左下走i步就是x,y同时减去i,那么这就是一个裸的威佐夫博奕:有两堆各若干个物品,两个人轮流从某一堆或同时从两堆中取同样多的物品,规定每次至少取一个,多者不限,最后取光者得胜。

代码:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main()
{
    int n,m;
    double x=(1.0+sqrt(5.0));
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n>m)
            swap(n,m);
        int temp=floor((m-n)*x/2.0);
        if(temp==n) printf("Lao Wang\n");
        else printf("Xiao Ren\n");
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值