1. 程式人生 > >NOIP2018普及組題解

NOIP2018普及組題解

雖然已經是TG選手了,但是作為一個初中生還是要做做PJ噠>_<.

 

T1:

題目:luogu5015.

題目大意:將輸入的字元中的小寫字母、大寫字母和數字的總數量輸出.

水題,用getchar水過就可以了.

程式碼如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL; 

int sum;

Abigail into(){
  char c=getchar();
  while (c!=EOF){
    if (c<='Z'&&c>='A'||c<='z'&&c>='a'||c<='9'&&c>='0') ++sum;
    c=getchar();
  }
}

Abigail outo(){
  printf("%d\n",sum);
} 

int main(){
  into();
  outo();
  return 0;
}

 

T2:

題目:luogu5016.

題目大意:給定一個數組A,現在在第m個點前的都屬於龍,後面的都屬於虎,第m個不屬於任意一方.現在給Ai加了s1,要求再給一個Aj加s2,使得兩方氣勢差最小.其中一方的氣勢定義為\sum Ai*|m-i|.

其實這道題就是算出兩方的氣勢,然後暴力列舉加入的位置就可以了.

不懂為什麼一群人說被long long坑了,這不是明顯要開long long嗎...

程式碼如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=100000;
const LL INF=(1LL<<63)-1LL;      //寫成1LL<<60的我被卡精度了

LL a[N+9],sum1,sum2,a1,a2,ans;
int n,m,p,v;

Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;++i) scanf("%lld",&a[i]);
  scanf("%d%d%lld%lld",&m,&p,&a1,&a2);
}

Abigail work(){
  a[p]+=a1;
  for (int i=1;i<m;++i)
    sum1+=a[i]*(m-i);
  for (int i=m+1;i<=n;++i)
    sum2+=a[i]*(i-m);
  ans=INF;v=1;
  for (int i=1;i<m;++i)
    if (ans>abs(sum1+a2*(m-i)-sum2)) ans=abs(sum1+a2*(m-i)-sum2),v=i;
  if (ans>abs(sum1-sum2)) ans=abs(sum1-sum2),v=m;
  for (int i=m+1;i<=n;++i)
    if (ans>abs(sum1-sum2-a2*(i-m))) ans=abs(sum1-sum2-a2*(i-m)),v=i;
}

Abigail outo(){
  printf("%d\n",v);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

 

T3:

題目:luogu5017.

題目大意:給定一輛車往返一趟的時間m,並且車的容量無限大.現在要送n個人,人i會在時刻ti到達車站,問車這n個人等車時間之和最小是多少.

據說可以以時間為狀態,然後斜率優化一下可以做到O(max \left \{ t[i] \right \}),但是我不會.

我們可以設f[i]表示送前i個同學需要的最少等待時間,然後直接列舉從前面那個狀態轉移過來,可以做到O(n^2).

但是很明顯這個演算法是不滿足最優子結構的,因為總等待時間最短不一定代表回到起點的時間點最早.

我們考慮先預處理兩個陣列c[i]和s[i],分別表示到時刻i時的人的數量與乘客到達時間的字首和.

那麼我們考慮設f[i]表示時刻i發一輛車時的最少總等待時間,然後列舉上一次的發車時刻j來轉移,時間複雜度O(max \left \{ t[i] \right \}^2)

.

對於這個演算法,我們可以列出方程:

f[i]=min_{j=0}^{i-m}(f[j]+i*(c[i]-c[j])-s[i]+s[j])

考慮一個狀態f[i]從f[j]轉移過來,可以發現若i-j>2m,明顯可以讓這兩次發車之間多發一次車來使答案不會變得更劣,那麼我們就可以將列舉j變成列舉時差來優化上面的演算法,時間複雜度O(m*max \left \{ t[i] \right \}).

那麼方程就變為:

f[i]=min_{j=m}^{2m}(f[i-j]+i*(c[i]-c[i-j])-s[i]+s[i-j])

繼續考慮優化這個演算法,發現一輛車的發車時間只會是t[i]+j,其中0\leq j\leq m.那麼我們可以只計算有用的狀態,不去計算無用的狀態,也就是說我們列舉到一個狀態f[i]時,判斷這個狀態是否有一個t[j],使得t[j]\leq i\leq m,具體可以通過c[i]-c[i-m]是否為0來判斷.這樣我們就可以做到O(nm^2+max \left \{ t[i] \right \}).

然後貌似還可以把這個演算法優化一下,類似於斜率優化,可以做到O(nm+nlogn),但是我不會.

O(nm^2+max \left \{ t[i] \right \})演算法程式碼如下:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=500,M=100,T=5*1000000;
const int INF=(1<<29)-1;

int n,m,t[N+9];
int c[T+9],s[T+9],mt;
//int f[N+9][M*2+9];
int f[T+9];

Abigail into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=n;++i){
    scanf("%d",&t[i]);
    mt=max(mt,t[i]);
    ++c[t[i]];s[t[i]]+=t[i];
  }
}

Abigail work(){
  mt+=m-1;
  sort(t+1,t+1+n);
  for (int i=1;i<=mt;++i)
    c[i]+=c[i-1],s[i]+=s[i-1];
  for (int i=0;i<=mt;++i)
    f[i]=i*c[i]-s[i];
  for (int i=0;i<=mt;++i){
    if (!(c[i]-c[i-m])&&i>m){
      f[i]=f[i-m];      //避免一些麻煩的處理 
      continue;
    }
    f[i]=i*c[i]-s[i];
    for (int j=max(0,i-(m<<1));j<=i-m;++j)
      f[i]=min(f[i],f[j]+i*(c[i]-c[j])-s[i]+s[j]);
  }
}

Abigail outo(){
  int ans=INF;
  for (int i=t[n];i<=mt;++i)
    ans=min(ans,f[i]);
  printf("%d\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}

 

T4:

題目:luogu5018.

題目大意:給定一棵二叉樹,要求一棵最大的子樹,使得這棵子樹翻轉過後與原子樹一模一樣(點權、結構、大小等).

感覺很難的一道題,也沒做過類似的題,看到題面嚇懵了,PJ考的都是什麼毒瘤題啊.

事實上,我們只要考慮以每個點為根,暴力判斷這棵子樹是否對稱,並在判定的時候及時彈出.這樣看起來是O(n^2)的,事實上是O(nlogn)的.

為什麼呢?首先我們在遍歷的時候,每次判定,我們只要一旦發現就夠不同就會彈出,那麼很顯然最壞的情況肯定是當一棵樹是完全二叉樹的時候.而完全二叉樹的深度是O(logn)的,我們在每一層都O(n)掃了一遍節點,所以時間複雜度為O(nlogn).

具體各種剪枝看程式碼:

#include<bits/stdc++.h>
  using namespace std;

#define Abigail inline void
typedef long long LL;

const int N=1000000;

int n,v[N+9],ls[N+9],rs[N+9],siz[N+9],ans;

void dfs(int k){
  siz[k]=1;
  if (ls[k]) dfs(ls[k]);
  if (rs[k]) dfs(rs[k]);
  siz[k]+=siz[ls[k]]+siz[rs[k]];
}

bool check(int r1,int r2){
  if (r1+r2==0) return true;      //若r1=r2=0,則匹配 
  if (v[r1]^v[r2]) return false;      //兩邊點權是否相同 
  if ((siz[ls[r1]]^siz[rs[r2]])||(siz[rs[r1]]^siz[ls[r2]])) return false;      //兩邊是否大小/結構對稱 
  if (!check(ls[r1],rs[r2])) return false;      //若r1的左兒子與r2的右兒子不匹配 
  if (!check(rs[r1],ls[r2])) return false;      //若r1的右兒子與r2的左兒子不匹配 
  return true;
}

Abigail into(){
  scanf("%d",&n);
  for (int i=1;i<=n;++i) scanf("%d",&v[i]);
  for (int i=1;i<=n;++i){
    scanf("%d%d",&ls[i],&rs[i]);
    if (ls[i]==-1) ls[i]=0;
    if (rs[i]==-1) rs[i]=0;
  }
}

Abigail work(){
  dfs(1);
  for (int i=1;i<=n;++i)
    if (check(i,i)) ans=max(ans,siz[i]);
}

Abigail outo(){
  printf("%d\n",ans);
}

int main(){
  into();
  work();
  outo();
  return 0;
}