UVa的几道水题题解

来源:互联网 发布:知乎账号购买 编辑:程序博客网 时间:2024/05/29 19:43

And Then There Was One, Japan 2007,LA 3882 题解

Vjudge传送门


题意:

n个数排成一圈,第一次删除m,以后没k个数删除一次,求最后一个被删除的数
数据范围:多组数据,2<=n<=100001<=k<=100001<=m<=n


题解:

显然我们已经不能模拟了,因为数据规模实在是太大啦,已经不再是猴子选大王靠模拟就可以搞出来的了。然而怎么做呢,我们可以用递推搞出来嘛?仿佛是可以的,因为如果是n个猴子选大王,第一个猴子被踢出去之后,剩下不就是n1个猴子嘛?所以我们完全可以重新编个号,从n个猴子中被踢出去的那只猴子之后重新开始编12345...那么f[n1]的答案不是可以直接+k就是原来的编号吗?因为在重新编号的时候所有的编号都被k了,所以我们就可以愉快地递推了,注意%n即可。


代码:

#include<cstdio>#include<cstring>#include<iostream>using namespace std;int n,k,m,f[10000+1000];int main(){    while(scanf("%d%d%d",&n,&k,&m)!=EOF&&n&&k&&m){        f[1]=0;        for(register int i=2;i<=n;i++)f[i]=(f[i-1]+k)%i;        int ans=(m-k+1+f[n])%n;        if(ans<=0) ans+=n;        printf("%d\n",ans);    }    return 0;}

UVaLive 3905 Meteor ,Seoul 2007 题解

Vjudge传送门


题意:

给你n个点,然后这n个点会按照给出的x轴速度,y轴速度运动,我们需要求得是,现在给你一个固定的相框,输出n个点出现在这个固定的相框里最多的时候一共有n个中的多少个点


题解:

如果直接正面去求这个,会发现很难算,所以我们需要转化思路,既然是求n个点中某一时刻尽量出现更多的点在这个框里,那么我们的想法是逆向思维,先把n个点出现的时刻先计算出来,然后再记录到一个结构体里面,即一个区间l,r,表示这个点在l,r这段时间内出现次数最多,于是我们可以得出一个算法,求解在某一时刻,重叠的区间数最多,输出区间最多是多少个,显然我们可以把左端点和右端点当做两个事件,也就是说,对所有事件排了序之后我们从左至右扫描(因为时间是向量,只能线性地流逝不能纵向叠加[当然对于蛤来说除外]),如果扫到一个左端点,那么说明新进来了一个区间,如果扫到一个右端点,那么说明出去了一个区间,注意一点有点坑,也就是这道题在框上的点是不算的,因此我们需要把右端点的事件的优先级设置为比左端点大,也就是说倘若是同一个时间,一个是左端点,一个是右端点,那么右端点这个时间的优先级高于左端点,这样一来会先扫描到右端点,再扫描到左端点,也就是先出再进,才能保证答案的正确性,至此我们就已经得到了完整的算法,在计算一个点在框内的时间的时候有一个小技巧,也就是先计算x坐标位于相框区间的时间,再计算y坐标位于相框区间的时间,这样两个时间的交集显然就是x坐标位于相框区间,y坐标位于相框区间的时刻,这道题貌似没有eps的问题,但是其实可以转化成整数,貌似是同时乘以一个什么公倍数。


代码:

#include<cstdio>#include<cstring>#include<algorithm>#include<iostream>using namespace std;void update(int x,int a,int w,double& L,double& R){    if(a==0){        if(x<=0||x>=w) R=L-1;    }else if(a>0){        L=max(L,-(double)x/a);        R=min(R,(double)(w-x)/a);    }else{        L=max(L,(double)(w-x)/a);        R=min(R,-(double)x/a);    }}const int MAXN=100000+10;struct Event{    double x;int type;    bool operator < (const Event& a) const{return x<a.x||(x==a.x&&type>a.type);}}event[MAXN*2];int main(){     int T;    scanf("%d",&T);    while(T--){        int w,h,n,tail=0;        scanf("%d%d%d",&w,&h,&n);        for(register int i=1;i<=n;i++){            int x,y,a,b;            scanf("%d%d%d%d",&x,&y,&a,&b);            double L=0,R=1e9;            update(x,a,w,L,R);            update(y,b,h,L,R);            if(R>L){                event[++tail]=(Event){L,0};                event[++tail]=(Event){R,1};            }        }        sort(event+1,event+tail+1);        int cnt=0,ans=0;        for(register int i=1;i<=tail;i++){            if(event[i].type==0) ans=max(ans,++cnt);            else                 cnt--;        }        printf("%d\n",ans);    }    return 0;}

Jurassic Remains,NEERC 2003,LA 2965 题解


Vjudge传送门


题意:

大概的题意是指现在有n个由大写字母组成的字符串,选择尽量多的穿,是的每个大写字母都出现了偶数次


题解:

最开始我想的就是bitset暴力,说不定还能卡过,因为(1<=n<=24),所以我们直接bitset说不定也是可以的//直接穷举应该就可以了233.复杂度是224卡点常数。我们还有一个比较优美的方法,是把前一半的序列的所有可能搞出来,然后一个集合对应的尽量多的字符串,于是我们用map来维护一下,然后对于后半部分,我们边计算边查找,然后ans不断地取max就可以了,写一个求二进制位有多少个1的函数。时间复杂度降到了2n/2log2n,更加优美,这种思路是密码学中很著名的中途相遇攻击法(Meet-in-the-Middle Attack)


代码:

#include<cstdio>#include<iostream>#include<cstring>#include<map>using namespace std;map<int,int>mp;const int MAXN=100+10;int bitcount(int x){    int cnt=0;    while(x){if(x&1!=0) cnt++; x>>=1;}    return cnt;}int main(){    int n,A[MAXN];    char s[1000];    while(scanf("%d",&n)==1&&n){        for(register int i=0;i<=n-1;i++){            scanf("%s",s);            A[i]=0;            for(register int j=0;s[j]!='\0';j++) A[i]^=(1<<(s[j]-'A'));        }        mp.clear();        int n1=n/2,n2=n-n1;        for(register int i=0;i<(1<<n1);i++){            int x=0;//表示一种集合的异或和             for(register int j=0;j<n1;j++) if(i&(1<<j)) x^=A[j];            if(!mp.count(x)||bitcount(mp[x])<bitcount(i)) mp[x]=i;        }        int ans=0;        for(register int i=0;i<(1<<n2);i++){            int x=0;            for(register int j=0;j<n2;j++) if(i&(1<<j))x^=A[n1+j];            if(mp.count(x)&&bitcount(ans)<bitcount(mp[x])+bitcount(i)) ans=(i<<n1)^mp[x];        }        printf("%d\n",bitcount(ans));        for(register int i=0;i<n;i++) if(ans&(1<<i)) printf("%d ",i+1);        printf("\n");    }    return 0;} 

Prince ans Princess,UVa 10635题解

Vjudge传送门


题意:

现在有两条长度为p+1和q+1的序列,每个序列的元素都各不相同,都是1n2的数,求两个序列的LCS,p,q的范围是n2内,n的范围是250,多组数据


题解:

LCS我貌似只会pq的写法,显然是要挂的(尤其是ccf老年机),所以我们想一下其他解法来解LCS,显然这道题有一个性质,也就是无论是第一条序列还是第二条序列,他们的元素都是互异的,因此我们的思路是先给A数组重新编号,从1开始,一直编到A的长度,对于B数组,如果这个位置的数在A中出现过,那么就应该是这个数在A中出现的位置的值,如果没有出现过就是0,然后我们求一下B的LIS就可以了,LIS貌似我们是可以nlogn做的,所以这道题就迎刃而解了。
其实刚才说的这个道理是很清楚的,因为我们把所有的B数组都变成了A数组中该数字出现的位置,那么如果找出一个连续递增的B数组,一定可以找到A和B的LCS为B的LIS所表示的这个LCS,也即B中找到的序列,在A中的位置要递增,显然吧如果在B中位置是递增的但是在A中位置不递增,怎么LCS?所以这道题就这么转化啦,非常愉快


代码:


#include<cstdio>#include<iostream>#include<cstring>using namespace std;const int MAXN=250*250+10;const int INF=1000000000;int a[MAXN],b[MAXN],c[MAXN],num[MAXN],l[MAXN],g[MAXN],T,tail;int main(){    scanf("%d",&T);    for(register int kase=1;kase<=T;kase++){        int N,p,q,x;        scanf("%d%d%d",&N,&p,&q);        memset(c,0,sizeof(c));        for(register int i=1;i<=p+1;i++) scanf("%d",&a[i]),c[a[i]]=i;        for(register int i=1;i<=q+1;i++){            scanf("%d",&b[i]),b[i]=c[b[i]];            if(b[i]!=0) num[++tail]=b[i];        }        int ans=0;        for(register int i=1;i<=tail;i++) g[i]=INF;        for(register int i=1;i<=tail;i++){            int k=lower_bound(g+1,g+tail+1,num[i])-g;            l[i]=k;g[k]=num[i];            ans=max(ans,l[i]);        }        printf("Case %d: %d\n",kase,ans);    }    return 0;}/*1 3 6 71 7 5 4 8 3 91 4 3 5 6 2 8 9*/

Game of Sum,UVa 10891题解

题意:

有一个长度为n的序列,然后这个序列的元素可正可负,现在要A和B轮流取数,每次可以取左边或者右边的任意多个数,当然你可以在某一次把这个序列直接取完,A先取,求A的得分减去B的得分后的结果


题解:

显然这道题如果问的不是是A取的分减去B取的分而是A取的分加上B取的分的话,会好做得多(这不是废话嘛)…
也就是说,A和B取的分的和是一定的,就看谁取得多,谁取的少,但是总和是一定的,所以到这里我们大概已经想到了转移的方法,另一个dp[i][j]表示i到j这个区间,先手能够获得的最大收益,那么显然我们可以初始化dp数组为dp[i][i]=a[i],然后我们会发现每次转移的时候,我们可以枚举一下i和j中间的点,使得前一段先手的人拿然后留后一段给另一个人先手,或者后一段先手的人拿留前一段给另一个人先手,所以我们可以推出

dp[i][j]=sum[i][j]min(dp[i][i],dp[i][i+1]...dp[i][j1],dp[j][j],dp[j1][j]...dp[i+1][j],0)

然后我们每次枚举转移就可以了,但是据说还有一种可以优化到n2的方法,就是记一个f数组和一个g数组,分别表示dp数组前一段区间的最小值和dp数组后一段区间的最小值,然后转移就变成了O(1)的,不过貌似不是很必要,但是很优美是承认的233


代码:

#include<cstdio>#include<iostream>#include<cstring>using namespace std;const int MAXN=100+10;int n,b[MAXN],a[MAXN],dp[MAXN][MAXN];//4//4 -10 -20 7 int main(){    while(scanf("%d",&n)==1&&n){        for(int i=1;i<=105;i++){            for(int j=1;j<=105;j++){                dp[i][j]=-100000000;            }        }        b[0]=0;        for(register int i=1;i<=n;i++){scanf("%d",&a[i]);dp[i][i]=a[i];b[i]=b[i-1]+a[i];}        for(register int len=2;len<=n;len++){            for(register int i=1;i<=n-len+1;i++){                int j=i+len-1;                                                          for(register int k=i+1;k<=j;k++){dp[i][j]=max(dp[i][j],b[j]-b[i-1]-dp[k][j]);}                dp[i][j]=max(dp[i][j],b[j]-b[i-1]);                for(register int k=i;k<j;k++)dp[i][j]=max(dp[i][j],b[j]-b[i-1]-dp[i][k]);            }        }        printf("%d\n",2*dp[1][n]-b[n]);    }    return 0;}

Calculator Conundrum,UVa 11549题解


题意&题解:

有一个神奇的计算器,然后每次计算之后只显示前n位,比如n为5的时候2147483647显示21474,然后我们只需要处理一下long long然后转字符串然后再转回来就行了,然后突然发现可以直接除啊…我是不是傻…竟然copy了刘汝佳的代码


代码:

#include<cstdio>#include<cstring>#include<iostream>#include<algorithm>using namespace std;int buf[10],T,n,k;int next(int n,int k){    if(!k) return 0;    long long k2=(long long)k*k;    int L=0;    while(k2>0){buf[L++]=k2%10;k2/=10;}    if(n>L) n=L;    int ans=0;    for(register int i=0;i<n;i++) ans=ans*10+buf[--L];    return ans;}int main(){    scanf("%d",&T);    while(T--){        scanf("%d%d",&n,&k);        int ans=k;        int k1=k,k2=k;        do{            k1=next(n,k1);            k2=next(n,k2);if(k2>ans) ans=k2;            k2=next(n,k2);if(k2>ans) ans=k2;        }while(k1!=k2);        printf("%d\n",ans);    }    return 0;}

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

原创粉丝点击