链接:https://www.nowcoder.com/acm/contest/116#question
C | 勤奋的杨老师 |
解析:从左到右两次求最长子序列就行,但是这里要用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; }
A | Red Rover |
解析:看到字符串比较小,所以暴力枚举子串再用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; }
H | XOR |
题意:现有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; } }
G | chess |
题意:有一个棋盘,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; }