题目链接
感受:这次一共做出四道题。感觉自己的思维还是不那么灵活,在A题卡了半小时(丢人)。前二十分钟看的A题感觉有思路又感觉没思路,索性直接看B,两分钟AC了,然后又看了两分钟A,还是写不出,又看了C,做了十分钟AC 了,这时候看了眼D,觉得应该出的不会快,然后决定静心看A,好好分析了几分钟AC了。这时候还一个小时,先是看了一眼E题,一个图论题,一开始的想法是并查集判环,但写到一半发现我处理不了一些细节,然后就放弃了去看D。D题给我的感觉是像一个贪心,分析了一下发现当前决策会影响下面的决策,所以贪心否定了。那么只剩下暴力和dp了,但dp的话免不了遍历所有情况,与其dp不如直接暴力,然后写了个搜索,看到数据量有点大,怕超时。分析了十分钟,发现一直递归下去到停止条件的话,2e5的数据量顶多递归二十多层,这种复杂度是可以接受的,
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)的复杂度交上去AC了。。当即就感觉D题好水。。。然后直接睡觉了。。
总结:有些图论题可能是没有系统训练的缘故把,好多细节我处理不了,然后对时间复杂度的分析也很重要,如果不分析的话那个D题我肯定不会交的。稍微分析一下发现是可行的。
A题
题目大意:给你三个数x,y,z。让你求三个数a,b,c。条件是x=max(a,b),y=max(a,c),z=max(b,c).存在这三个数就输出yes并输出这些书,否则no
思路:小小分析一波,因为输出顺序是任意的,所以我们只需要按自己的意愿去构造就行了,把xyz从大到小排序。首先x是ab最大值,y是ac最大值,都含有a,我们假设a最大,所以x一定等于y。b和c一定小于a,所以把z赋给b,那么c就任意取一个最小值就行了,取1就ok了。
一开始卡在这个题也是自己分析不到位把。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=2e5+10;
int main()
{
int t;
cin>>t;
while(t--)
{
int x,y,z;
cin>>x>>y>>z;
if((x>y&&x>z)||(y>x&&y>z)||(z>x&&z>y))
{
cout<<"NO"<<endl;
continue;
}
if(x<y) swap(x,y);
if(x<z) swap(x,z);
if(y<z) swap(y,z);
int a,b,c;
a=x;b=z;c=1;
cout<<"YES"<<endl;
cout<<a<<" "<<b<<" "<<c<<endl;
}
return 0;
}
B题
题目大意:给你两个已经相互插入的数字序列,让你求出最原始的那个数字序列。
思路:这个很简单,因为这些数字序列顺序的固定的,所以我们从左往右扫过去,对于没出现过的数字我们就把他扔到答案里,然后标记这个数字出现过。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=2e5+10;
int a[N],ans[N];
int main()
{
int t;
cin>>t;
while(t--)
{
map<int,int>f;
int n,cnt=0;
cin>>n;
for(int i=1;i<=2*n;i++) cin>>a[i];
for(int i=1;i<=2*n;i++)
{
if(!f[a[i]])
{
f[a[i]]=1;
ans[++cnt]=a[i];
}
if(cnt==n) break;
}
for(int i=1;i<=cnt;i++) cout<<ans[i]<<" ";
cout<<endl;
}
return 0;
}
C题
题目大意:给你一个序列,问最少去掉多少个前缀元素能得到一个“好序列”。“好序列”是这样定义的:每次能从一个序列的左右两边取数,若能取出一个非递减序列则是“好序列”。
思路:先分析什么样的序列能够叫做好序列,首先假设这个序列是b1,b2,…bm…bn。那么bm一定是最大的元素,然后从左到右是非减序列,从bm到bn是非增序列。为什么呢,假设b2<b1.那么你无论怎么取数b2一定在b1的后面形成递减的趋势,这是不被允许的。所以我们思路也很明确了,因为是去掉前缀,所以我们就找最长“好序列”,从后往前找,找到第一个不满足从后往前递增条件的元素,然后从这个元素往前找找到开始升的那个元素就停止最后答案就是n减去最长“好序列”长度。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=2e5+10;
int a[N];
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int right=n;
while(a[right-1]>=a[right]&&right>=1) right--;
//cout<<right<<endl;
while(a[right-1]<=a[right]&&right>=1) right--;
int ans=right-1;//这个式子是最后化简出来的一个最简的式子。
if(ans==-1) ans=0;
cout<<ans<<endl;
}
return 0;
}
D题
题目大意:好难描述啊。首先给你一个字符串,让你用最少的操作将他变成“a-好字符串”。那么什么是“a-好字符串”呢。就是前半部分全是a,后半部分是“(a+1)-好字符串”,然后依次递推下去。具体还是看题目里的解释吧。
思路:一眼看上去肯定觉得是个贪心,每次将拥有当前需要字符最多的那边变成指定字符,但是这样是不行的,不满足无后效性。所以考虑dp和暴力,但我们都知道dp也可以算是一种较为优雅的暴力,与其dp不如直接深搜呢。所以我们可以枚举所有情况,最后取最小值就行了。具体的还是代码写的清楚。
这个题教会我们的应该不是思路,而是对复杂度的分析,不分析乍一看很像超时代码,实则不然。2e5的数据量每次递归一半,最多递归二十多层nlogn的复杂度完全可以。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int N=2e5+10;
char s[N];
int solve(int l,int r,char m)
{
if(l==r)
{
if(s[l]==m) return 0;
else return 1;
}
int mid=(l+r)>>1,left=0,right=0,len=r-l+1;
for(int i=l;i<=mid;i++) if(s[i]==m) left++;
for(int i=mid+1;i<=r;i++) if(s[i]==m) right++;
int ans=min(len/2-left+solve(mid+1,r,m+1),len/2-right+solve(l,mid,m+1));
return ans;
}
int main()
{
int t;
cin>>t;
while(t--)
{
int n;
char m='a';
cin>>n;
for(int i=1;i<=n;i++) cin>>s[i];
int ans=solve(1,n,m);
cout<<ans<<endl;
}
return 0;
}
E题
题目大意:给你一个n个顶点m条边的图,其中一些边没有反向,其余边有方向,让你对每一条边赋一个方向,要求最终图不能形成自环。(没有重边)
题解