題目連結:https://ac.nowcoder.com/acm/contest/94181
- A:StringGame (思維)
- B:SequenceGame (貪心+二分)
- C:照看小貓 (幾何,思維)
- D:小H的數列 (數學)
- E:小H的糖果 (思維)
- F:學姐的編碼1.0 (DP)
- G:學姐的編碼2.0 (DFS)
- H:迷陣 (BFS+雙指標)
- I:Rating (思維+優先佇列)
- J:字串修改 (暴力)
- K:New Game! (計算幾何+最短路)
A:StringGame (思維)
題意介紹:給出一個長度為n的字串s以及操作次數x,我們有一個操作,可以將字串s的第一個字元放在s的後面,並將第一個刪掉。問經過x次操作後,問字串s最後是什麼樣子的。
資料範圍: 1<=n<=1E5 1<=x<=1E18
思路:可以看成一個環,如果當操作了n次時,就又回到了原樣。所以我們要求的是在模n意義下的操作次數x,即x=x%n。然後我們就先輸出後面的n-x個字元,再輸出前x個字元即可。
複雜度:O(n)
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
int main(){
long long n,x;
string s;
cin>>n>>x;
cin>>s;
x=x%n;
if(x==0)
cout<<s<<endl;
else
cout<<s.substr(x,n-x)<<s.substr(0,x)<<endl;
}
B:SequenceGame (貪心+二分)
題意介紹:簡單來說,就是給你n組數,每組數有m個字,問當n組數中只能取一個數字或者不取時,所形成的最長上升子序列的長度是多少。
資料範圍:n<1E4 a[i]<=10
思路:類似於最長上升子序列的貪心+二分做法,唯一的不同點就是多了一個分組。對於最長上升子序列,我們的做法是,建立一個陣列f,下標表示長度,元素值代表在這個長度下末尾的最小元素值。既然如此,我們在此基礎上,建立一個臨時的陣列f1,具體的含義也是如上所示。但是,我們二分的時候,還是對於原來的f進行操作,對於f1進行更新,這樣就避免更新的操作影響到組內元素的操作,最後再用f1對於f進行相應的更新即可。例如,一組數 1,2,3。無論如何,最長上升子序列的長度也只能為1,但是,如果不建立臨時的陣列f1,而是直接對於f進行修改的話,那麼在操作後f1 = 1,f1 = 2,f1 = 3。而如果建立了臨時的f1陣列,就可以避免修改對於當前組內的後續操作造成影響,這樣操作的結果就是f1 = 1。其餘的步驟,均和最長上升子序列一致。
複雜度:O(nmlogn)
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,m;
cin>>m>>n;
vector<vector<int>>a(n+1,vector<int>(m+1,0));
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
int len=0;
vector<int>f(n+1,90000000);
vector<int>f1(n+1,90000000);
for(int i=1;i<=n;i++){
int templen=len;
for(int j=1;j<=len;j++){
f1[j]=f[j];
}
for(int j=1;j<=m;j++){
int l=1,r=templen+1,mid;
while(l<r){
mid=(l+r)>>1;
if(f[mid]>=a[i][j])r=mid;
else l=mid+1;
}
f1[l]=min(f1[l],a[i][j]);
len=max(len,l);
}
for(int j=1;j<=len;j++){
f[j]=f1[j];
}
}
cout<<len<<endl;
}
C:照看小貓 (幾何,思維)
題意介紹:給出n個點,這些點全部位於第一象限以及x,y的正半軸。我們可以在這些點當中任意連線,在滿足所連線段和x,y正半軸能夠組成封閉圖形的前提下,線段的長度和最小。
資料範圍: \(1 \le n \le 10^5 , 0 \le x_i,y_i \le 10^9\),同時不存在(0,0)
思路:首先,要是能圍住,那麼一定在x,y正半軸上面至少各有一個點,否則無法練成。那麼最小的長度,肯定是距離x軸最近的y軸上的點,以及距離y軸最近的x軸上的點,這兩點所形成的線段最短。因為三角形,兩邊之和大於第三邊。如果要結合其他點,那必然會形成三角形,就會大於這條線段。
(PS:噁心之處,不關閉同步流,會出現超時現象)
複雜度:O(n)
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
int main(){
ios;
int n;
double x=1E18,y=1E18,u,v;
int f1=0,f2=0;
cin>>n;
for(int i=1;i<=n;i++){
cin>>u>>v;
if(u==0){
f1=1;
y=min(y,v);
}
if(v==0){
f2=1;
x=min(u,x);
}
}
if(f1&&f2){
cout<<fixed<<setprecision(6)<<sqrtl(x*x+y*y)<<'\n';
}
else {
cout<<"Poor Little H!"<<'\n';
}
}
D:小H的數列 (數學)
題意介紹:一個數列滿足a1 = 1,4ai-1ai = (ai-1 +ai -1)2。給出數字n,求出第n項的值。
資料範圍:n<1E(10086)
思路: 把上面的公式化簡一下,就可以得到\(\sqrt[]a_i\)-\(\sqrt[]{a_{i-1}}=1\),我們不難看出,這個其實是完全平方數,因為只有完全平方數開平方之後的值相減才是1。我們直接輸出n的平方即可,但是考慮到資料範圍,我們不如直接用python,省得去找C++高精度的模板。
複雜度:O(1)
程式碼實現:
n=int(input())
print(n*n)
E:小H的糖果 (思維)
題意介紹:總共有T組測試,每組給出兩個整數k,n。我們一開始的數字是1,我們可以進行兩種操作,第一種是直接+1,第二種是*k。求從1變化到n的最小操作次數是多少。
資料範圍:n<1E4 a[i]<=10
思路:這題是思維題,當k為1的時候,我們採取第二種操作其實沒有任何意義,因此我們直接輸出n-1即可。而當k不等於1的時候,我們反向計算,如果n大於等於k的時候,我們直接先求要減去多少次,才可以使得當前的n是k的倍數,即ans=ans+n%k+1,然後我們再更新n,讓n整除k。由此進行,n最終的值只能小於k。這個時候答案其實很明顯,就是ans+k-1。那麼為什麼這個方法是可以的呢?首先,我們知道k的變化速度可能要大於+1,那麼肯定能使用k越多越好,但是這樣又會存在萬一多乘了該怎麼辦,我們不存在-1的操作。這個時候我們不如反向思考,既然我們從小可以得到大,我們也會再某個時候+1,那不如直接就反過來,如果能整除k,肯定優先整除,否則減去再整除,這樣我們既可以在保證不會多計算的同時次數最少。相當於一個數的k進位制一般。這個才是思考的關鍵部分。在前面部分的+1,其實就是在更高的進位制位上面+1,因為我們後面會存在*k操作。
(PS:這題還有個噁心點就是,不能使用endl,只能用“\n”,否則會超時)。
複雜度:O(Tlogn)
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
long long t;
long long k,n,ans;
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>t;
for(int i=1;i<=t;i++){
cin>>k>>n;
if(k==1){
cout<<n-1<<'\n';
continue;
}
ans=0;
while(n>=k){
ans+=n%k;
ans++;
n/=k;
}
cout<<ans+n-1<<'\n';
}
}
F:學姐的編碼1.0 (DP)
題意介紹:給出一個長度為n的字串,字串由十六進位制編碼組成。求不重複的遞增子序列有多少個?
資料範圍: \(1 \le n \le 10^6\)
思路:首先,我們考慮當第i個字元是B時,我們考慮前i-1個字元,如果存在十六進位制下小於B的字元,那麼我們以B結尾的子序列,就是前面以十六進位制下小於B的字元結尾的子序列之和+1。這樣的情況下,如果後面又再次遇到B的時候,會出現重複的情況,但我們可以注意到的是,前一個B所形成的子序列,肯定是後一個B所形成子序列的子集,也就是說後面的包含了前面的。如果那麼我們不妨每次再次遇到相同字母的時候,進行一個初始化,然後再進行操作即可。
複雜度:O(16n)
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
int main(){
string s;
cin>>s;
vector<int>a(17,0);
int n=s.size();
s=' '+s;
for(int i=1;i<=n;i++){
if(s[i]>='A'&&s[i]<='F'){
a[s[i]-'A'+11]=1;
for(int j=0;j<s[i]-'A'+11;j++){
a[s[i]-'A'+11]+=a[j];
}
}
else {
a[s[i]-'0']=1;
for(int j=0;j<s[i]-'0';j++){
a[s[i]-'0']+=a[j];
}
}
}
long long ans=0;
for(int i=0;i<17;i++){
//cout<<a[i]<<endl;
ans+=a[i];
}
cout<<ans<<endl;
}
G:學姐的編碼2.0 (DFS)
題意介紹:給出一個長度為n的字串,字串由十六進位制編碼組成。按字典序輸出所有不重複的遞增子序列。
資料範圍:\(1 \le n \le 10^6\)
思路:直接dfs暴力求解,但是直接找的話會超時,我們可以用二分法,判斷是否存在符合位置的下標。
複雜度:O(\(2^{16}logn\))
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
int ans[17];
vector<int>ff[17];
void dfs(int len,int x,int p){
for(int i=0;i<len;i++){
if(ans[i]<10)
cout<<ans[i];
else {
cout<<char(ans[i]-10+'A');
}
if(i==len-1){
cout<<endl;
}
}
for(int i=x+1;i<16;i++){
if(ff[i].size()){
int p1=upper_bound(ff[i].begin(), ff[i].end(), p)-ff[i].begin();
if(p1!=ff[i].size()){
ans[len]=i;
dfs(len+1,i,ff[i][p1]);
}
}
}
}
int main(){
string s;
cin>>s;
int n=s.size();
for(int i=0;i<n;i++){
if(s[i]>='0'&&s[i]<='9'){
ff[s[i]-'0'].push_back(i);
}
else {
ff[s[i]-'A'+10].push_back(i);
}
}
dfs(0,-1,-1);
}
H:迷陣 (BFS+雙指標)
題意介紹:有一個\(n \times n\) 的矩陣,每一個位置上的元素都有一個權值,每個位置都可以被經過人一次,要求從(1,1)到(n,n)所經過路徑的最大權值和最小權值之差是多少?
資料範圍:\(1 \le n \le 100 , w_{i,j} \le 3000\)
思路:一開始,我是想列舉最小值,然後二分最大值,接著bfs判斷能否到達,但是這樣會超時(3000* n* n* log(max\(a_i\)))(dfs不會。。。。。????)。所以參考別人的寫法,發現可以使用一種類似雙指標的方法去處理這道題。初始化l和r為0,先判斷能否到達,如果不能到達,則r++,直到能夠到達,當能夠到達時,則l++,直到不能到達。之所以這樣是可以的,是因為我們本質上是從小的l開始,找到第一個符合的r,然後再固定r,找到不符合的l,縮小了區間。然後再次迭代。因為最後肯定會存在一個l和r吧,這樣確保了l和r都會經歷到,相當於每次都找到了(l,r)符合的最小子段。
複雜度:O(6000nn)
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
int vis[101][101];
int vis1[101][101];
int n,maxx=0;
int dx[4]={1,-1,0,0};
int dy[4]={0,0,-1,1};
bool bfs(int l,int r){
memset(vis1,0,sizeof vis1);
queue<pair<int,int>>q;
q.push({1,1});
vis1[1][1]=1;
if(min(vis[1][1],vis[n][n])<l||max(vis[1][1],vis[n][n])>r){
return false;
}
int f=0;
while(q.size()){
if(q.front().first==n&&q.front().second==n){
return true;
}
for(int i=0;i<4;i++){
int x=q.front().first+dx[i];
int y=q.front().second+dy[i];
if(x>=1&&x<=n&&y>=1&&y<=n&&vis[x][y]<=r&&vis[x][y]>=l&&!vis1[x][y]){
vis1[x][y]=1;
q.push({x,y});
}
}
q.pop();
}
return false;
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
cin>>vis[i][j];
maxx=max(maxx,vis[i][j]);
}
}
int l=0,r=0;
int res=maxx;
while(l<=maxx&&r<=maxx){
if(bfs(l,r)&&r>=l){
//cout<<r<<' '<<l<<endl;
res=min(r-l,res);
l++;
}
else r++;
}
cout<<res<<endl;
}
I:Rating (思維+優先佇列)
題意介紹:首先,給出n,m,分別表n個cf號以及m場比賽表現分,每一次比賽,會選擇一個cf號的分數,與表現分相加併除以2。求最終的可能最高分時多少。
資料範圍:$n,m \le 10^5 $
思路:每一次都選擇最低分數,這樣萬一得分比最低分高,則總分增加最多,比最低低,則總分減少最少。這個,每次選擇最低分的過程,我們可以使用優先佇列。
複雜度:O(nlogn)
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
int main(){
priority_queue<double,vector<double>,greater<double>>q;
int n,m;
cin>>n>>m;
double ans=0;
for(int i=1;i<=n;i++){
double x;
cin>>x;
q.push(x);
ans+=x;
}
for(int i=1;i<=m;i++){
double x,y;
x=q.top();
q.pop();
cin>>y;
q.push((x+y)/2);
ans=ans-x+(x+y)/2;
cout<<fixed<<setprecision(2)<<ans<<endl;
}
}
J:字串修改 (暴力)
題意介紹:給出一個字串s,當下標為奇數時,\(s_i\)變為\(s_i + i\),否則,變為\(s_i\)變為\(s_i - i\)。字元加法被定義為a+1=b......z+1=a,減法同理。
資料範圍:\(1 \le n \le 10^5\)
思路:其實也是在模意義下,找出原先的數,即\(s_i - a\),然後在\(+-i\)之後,取模即可。
複雜度:O(n)
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
string s;
cin>>n;
cin>>s;
s=' '+s;
for(int i=1;i<=n;i++){
if(i&1){
cout<<(char)((s[i]-'a'+i)%26+'a');
}
else {
cout<<(char)(((s[i]-'a'-i)%26+26)%26+'a');
}
}
}
K:New Game! (計算幾何+最短路)
題意介紹:給出兩條平行直線和n個圓。在直線上和圓形中移動不消耗體力,否則消耗的體力為歐氏距離。求從直線l1到直線l2的最小花費體力。
資料範圍:\(1 \le n \le 1000\) 座標值的絕對值小於等於10000
思路:直接暴力計算出,將直線和圓形看成一個個地點,然後暴力計算出兩兩之間的距離,然後暴力進行Dijkstra一遍即可。
複雜度:O(n^2)
程式碼實現:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,a,b,c1,c2;
cin>>n>>a>>b>>c1>>c2;
vector<double>dis(n+2,1E9+7),x(n+2),y(n+2),r(n+2);
vector<vector<double>>edge(n+2,vector<double>(n+2,0.0));
edge[0][n+1]=abs(c1-c2)*1.0/sqrt(a*a+b*b);
edge[n+1][0]=abs(c1-c2)*1.0/sqrt(a*a+b*b);
for(int i=1;i<=n;i++){
cin>>x[i]>>y[i]>>r[i];
edge[0][i]=max(0.0,1.0*abs(a*x[i]+b*y[i]+c1)/sqrt(a*a+b*b)-r[i]);
edge[i][0]=max(0.0,1.0*abs(a*x[i]+b*y[i]+c1)/sqrt(a*a+b*b)-r[i]);
edge[i][n+1]=max(0.0,1.0*abs(a*x[i]+b*y[i]+c2)/sqrt(a*a+b*b)-r[i]);
edge[n+1][i]=max(0.0,1.0*abs(a*x[i]+b*y[i]+c2)/sqrt(a*a+b*b)-r[i]);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
edge[i][j]=max(0.0,1.0*sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]))-r[i]-r[j]);
}
}
dis[0]=0;
vector<bool>vis(n+2,0);
for(int i=0;i<=n+1;i++){
double maxd=1E9+10;
int p=-1;
for(int j=0;j<=n+1;j++){
if(!vis[j]&&maxd>dis[j]){
maxd=dis[j];
p=j;
}
}
//cout<<p<<' '<<dis[p]<<endl;
vis[p]=true;
for(int j=0;j<=n+1;j++){
dis[j]=min(dis[p]+edge[p][j],dis[j]);
}
}
cout<<dis[n+1]<<endl;
}