链接: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;
}