1017. 怪盗基德的滑翔翼
思路:f[i]来表示以a[i]为结尾的最长上升子序列。它有两个方向可以选择,当它往左边跳时,从左向右看跳过的那段序列要是单调递增的,就是在寻找最长公共子序列;当它往右边跳时,跳过的那段序列要是单调递减的,就是反过来从后往前找最长上升子序列。
include<iostream>
using namespace std;
const int N=110;
int a[N],f[N];
int main()
{
int k,n;
cin>>k;
while(k--)
{
cin>>n;
int res=0;
for(int i=1;i<=n;i++)
cin>>a[i];
//相当于往左边跳
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
{
f[i]=max(f[i],f[j]+1);
}
}
res=max(res,f[i]);
}
//相当于往右边跳
for(int i=n;i>0;i--)
{
f[i]=1;
for(int j=n;j>i;j--)
{
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
}
res=max(res,f[i]);
}
cout<<res<<endl;
}
return 0;
}
1014. 登山
思路:爬山的过程中,一旦往低处走了,就只能往更低处走,所以走的路线一定满足严格单调递增后单调递减的情况。所以以a[i]为分界点,分别算出以a[i]为结尾的最长上升子序列长度f[i]和逆向看以a[i]为结尾的最长上升子序列g[i],最后在把两个对应的相加起来比较求出最大值。
#include<iostream>
using namespace std;
const int N=1010;
int a[N],f[N],g[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int res=0;
for(int i=1;i<=n;i++)//递增的一块
{
f[i]=1;
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
}
}
for(int i=n;i>0;i--)//递减
{
g[i]=1;
for(int j=n;j>i;j--)
if(a[i]>a[j])
g[i]=max(g[i],g[j]+1);
}
for(int i=1;i<=n;i++)
res=max(res,f[i]+g[i]-1);//a[i]这个点在f[i],g[i]里都包含了
printf("%d\n",res);
}
482. 合唱队形
思路:与登山的思路是一样的,以每个点a[i]为分割点,求出它的左右两侧递增f[i]和递减g[i]的最长长度,最后把所有f[i]和g[i]一一相加,选出最大的一个,再用总人数减去它。这里与登山不同的是求最大和的时候并不是把同一点的f[i],g[i]加起来取最大值,而是让所有点的f[i],g[i]一一相加选最大的(它有多段分开的上升和下降子序列)。因为他是要取掉连续的一段,所以中间是会有断开的时候。
#include<iostream>
using namespace std;
const int N=110;
int a[N],f[N],g[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
{
f[i]=1;//记得初始化
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
f[i]=max(f[i],f[j]+1);
}
}
for(int i=n;i>0;i--)
{
g[i]=1;//记得初始化
for(int j=n;j>i;j--)
{
if(a[i]>a[j])
g[i]=max(g[i],g[j]+1);
}
}
int res=0;
for(int i=1;i<=n;i++)
{
for(int j=i;j<=n;j++)
{
if(i==j)
res=max(res,f[i]+g[j]-1);
else
res=max(res,f[i]+g[j]);
}
}
printf("%d\n",n-res);
return 0;
}
1012. 友好城市
思路:先对河岸的一边排序,再在另一边找最长上升子序列模型,即岸的另一边的坐标要在a[i]之前这一段是单调递增的,这样子保证了在岸的两边到a[i]这一段都是单调递增的,就不会有交叉的情况出现。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=5010;
int f[N];
typedef pair<int,int> PII;
PII a[N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i].first,&a[i].second);
sort(a,a+n+1);
int res=0;
for(int i=1;i<=n;i++)
{
f[i]=1;
for(int j=1;j<=i;j++)
{
if(a[i].second>a[j].second)
{
f[i]=max(f[i],f[j]+1);
}
}
res=max(res,f[i]);
}
printf("%d\n",res);
return 0;
}
1016. 最大上升子序列和
思路:把求最长上升子序列长度的长度改为每个数被选上的值。
#include<iostream>
using namespace std;
const int N=1010;
int f[N],a[N];
int main()
{
int n;
scanf("%d",&n);
int res=0;
for(int i=1;i<=n;i++)
{
f[i]=a[i];
for(int j=1;j<i;j++)
{
if(a[i]>a[j])
{
f[i]=max(f[i],f[j]+a[i]);
}
}
res=max(res,f[i]);
}
printf("%d\n",res);
return 0;
}
1010. 拦截导弹
思路:第一问就是求最长不上升子序列问题;第二问要求至少需要多少个序列才能覆盖所有的值。对于第二问采取贪心策略,每次让新加入的值放到已有子序列结尾小于新加入的并且是最小的里面,这样可以保证该序列后面可以放下更多的元素。
#include<iostream>
using namespace std;
const int N=1010;
int a[N],f[N],g[N];
int main()
{
int n=0,res=0;
while(~scanf("%d",&a[n])) n++;
for(int i=0;i<n;i++)
{
f[i]=1;
for(int j=0;j<i;j++)
{
if(a[i]<=a[j])
f[i]=max(f[i],f[j]+1);
}
res=max(res,f[i]);//能拦截的最多导弹
}
int cnt=0;//需要的系统数量
for(int i=0;i<n;i++)
{
int k=0;
while(k<cnt&&a[i]>g[k]) k++;//已经是保证了新加入的一定会加入到序列最小值的后面,当a[i]不大于已有的的所有子序列,
//那它就只能自己在创建一个新的序列,此事它最小,排在最后。
g[k]=a[i];
if(k>=cnt) cnt++;
}
printf("%d\n%d\n",res,cnt);
return 0;
}
272. 最长公共上升子序列
思路:f[i][j]表示所有在1~i和1~j中出现的公共子序列,且以b[j](a[i])结尾的。划分,①不包含a[i],即只在1~(i-1)和1~j中出现的f[i-1][j];②包含a[i]:此时a[i]和b[j]都包含了,这时不考虑a[i]和b[j]了,只考虑1~(i-1)和1~(j-1),考虑b的倒数第二个数可能是谁,那么可能为b[1],b[2]...[j-1]或者没有,此时表达式f[i][j]=f[i-1][k]+1(1<=k<j)。优化,怎么出去k这重循环?看f[i-1][1],f[i-1][2]...f[i-1][j-1],当j在进入下一次循环时,又有f[i-1][1],f[i-1][2]...f[i-1][j`-1],f[i-1][j-1],发现每次j进入循环时会重复计算上一次已经算过的,所以我们用一个maxn值来存储每次循环可以得到的一b[k]结尾的最大长度,即manx=max(maxn,f[i-1][k]+1),就可以避免重复计算多次了。
#include<iostream>
using namespace std;
const int N=3010;
int a[N],b[N],f[N][N];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
{
int maxn=1;//先置1表示f[i][j]以b[j]结尾的倒数第二个数b[k]为空的情况,那么就只有1种情况
for(int j=1;j<=n;j++)
{
f[i][j]=f[i-1][j];//不包含a[i]
if(a[i]==b[j])
{
f[i][j]=max(f[i][j],maxn);
}
if(b[j]<a[i])
maxn=max(maxn,f[i-1][j]+1);//maxn存储1~(j-1)中可以以b[j]结尾的最长长度
}
}
int res=0;
for(int i=1;i<=n;i++)
res=max(res,f[n][i]);
printf("%d\n",res);
}
Codeforces Round #531 (Div. 3)
B - Array K-Coloring
思路:用一个二维数组来标记每个数字被那种颜色图上了可以避免相同数字涂上相同的颜色。同时先让所有颜色都被使用过一遍,之后后面的只需要考虑不要让相同数字涂上相同的颜色即可。
#include<iostream>
using namespace std;
const int N=5010;
int a[N],sum[N],ans[N],count[N][N],visit[N];
int main()
{
int n,k,flag=0;
scanf("%d%d",&n,&k);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
sum[a[i]]++;
if(sum[a[i]]>k)
flag=1;
}
if(flag)
printf("NO\n");
else
{
int used=0;
for(int i=0;i<n;i++)
{
for(int j=1;j<=k;j++)
{
if(!count[a[i]][j]&&!visit[j]&&used<k)
{
count[a[i]][j]=1;//被使用过
ans[i]=j;
visit[j]=1;
used++;
break;
}
else if(used==k&&!count[a[i]][j])
{
ans[i]=j;
count[a[i]][j]=1;
break;
}
}
}
printf("YES\n");
for(int i=0;i<n;i++)
printf("%d ",ans[i]);
}
return 0;
}
C.Doors Breaking and Repairing
思路:当x>y时,一定可以使所有值变为0,因为无数次的轮流减x加y最后结果一定是小于等于0的。x<=y,一定是先把小于或等于x的值来减x,然后另一个一定会把小于等于x的值进行提升,一旦提升之后就不可能在减为0了。
#include<iostream>
using namespace std;
const int N=110;
int a[N];
int main()
{
int n,x,y;
scanf("%d%d%d",&n,&x,&y);
for(int i=0;i<n;i++)
scanf("%d",&a[i]);
if(x>y)
printf("%d\n",n);
else
{
int count=0;
for(int i=0;i<n;i++)
{
if(a[i]<=x)
count++;
}
if(count%2==0)
count/=2;
else
count=count/2+1;
printf("%d\n",count);
}
return 0;
}
D. Balanced Ternary String
思路:0,1,2最后的数量一定是n/3的,所以先逐个比较那个的数量大于n/3的,如果是0的话,那么用依次用2,1从后面开始取代0的位置直至0的数量为n/3;同理,1的话,先让0从前往后取代1,再让2从后往前取代1;2的话,让0,1依次从前往后取代2。
#include<iostream>
using namespace std;
const int N=3e5+10;
int n,a[N],b[N],c[N];
char ch[N];
int main()
{
scanf("%d%s",&n,ch);
int o=0,p=0,q=0;
for(int i=0;i<n;i++)
{
if(ch[i]=='0')
{
a[o++]=i;
}
else if(ch[i]=='1')
b[p++]=i;
else
c[q++]=i;
}
if(3*o>n)//0多
{
if(3*q<n)//2少
{
while(3*o>n&&3*q<n)
{
ch[a[--o]]='2';
q++;
}
}
if(3*p<n)
{
while(3*o>n&&3*p<n)
{
ch[a[--o]]='1';
p++;
}
}
}
if(3*p>n)//1多
{
int t1=0,t=p;
if(3*o<n)//0少
{
while(3*t>n&&3*o<n)
{
ch[b[t1++]]='0';
t--;o++;
}
}
if(3*q<n)
{
while(3*p>n&&3*q<n)
{
ch[b[--p]]='2';
q++;
}
}
}
if(3*q>n)//2多
{
int t=0;
if(3*o<n)
{
while(3*q>n&&3*o<n)
{
ch[c[t++]]='0';
q--;o++;
}
}
if(3*p<n)//1少
{
while(3*q>n&&3*p<n)
{
ch[c[t++]]='1';
p++;
q--;
}
}
}
printf("%s\n",ch);
return 0;
}
E - Monotonic Renumeration
思路:对于a[i]==a[j]的,则从i~j这一段数组b的值都要一样,所以题意就成了寻找有多少段不相交的区间。之后对于每段区间的取值只有两种,要么与下一个区间值相等,要么比它小1,所以对于m段区间,有2^(m-1)中可能(除去b[0]这一段不用考虑)。先从前往后遍历数值a,记录相同值的数的最远下标,之后在继续遍历一遍,即进行左右区间的比较来判断是不是同一区间的。这里用来离散化来把a的每一个值映射到下标从0~n。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N=2e5+10;
const int mod=998244353 ;
int n,a[N],t[N],r[N];
vector<int> alls;
int find(int x)
{
int l=0,r=alls.size()-1;
while(l<r)
{
int mid=l+r>>1;
if(alls[mid]>=x)
r=mid;
else
l=mid+1;
}
return l;
}
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
t[i]=a[i];
}
sort(t,t+n);//先排序
for(int i=0;i<n;i++)
{
alls.push_back(t[i]);
}
for(int i=0;i<n;i++)
{
a[i]=find(a[i]);//找到离散化后的下标
r[a[i]]=i;//记录区间的最右端点
}
int nowr=0,num=1;//
for(int i=0;i<n;i++)
{
if(i>nowr)//比较两个区间左右端点的位置,选择合并区间
{
num++;
}
nowr=max(nowr,r[a[i]]);
}
int res=1;
while(num-1)
{
res=res*2%mod;
num--;
}
printf("%d\n",res);
return 0;
}
Codeforces Round #530 (Div. 2)https://codeforces.ml/contest/1099
B. Squares and Segments
思路:每次画的时候往水平和垂直添边,使构成一个正方形,这样可以增加最少的边。小正方形的个数的是完全平方数的话,答案直接为sqrt(n)*2,否则用当前正方形的个数减去较外面的一个正方形的个数,可以得到剩下的正方形个数,判断这些个数要占用几列。
#include<iostream>
#include<algorithm>
#include<math.h>
using namespace std;
int main()
{
int n,ans;
scanf("%d",&n);
int tmp=sqrt((double)n);
if(sqrt((double)n)==tmp)
ans=2*tmp;
else
ans=2*tmp+(n-tmp*tmp)/tmp+((n-tmp*tmp)%tmp!=0);
printf("%d\n",ans);
return 0;
}