2019-2020Nowcoder Girl初賽題解


  寫了一天計算幾何,心態崩了,水一篇題解休息休息。

  emmmm,如果您是一名現役OIer/CSPer,那看這篇文章也許並不能在你的生命中留下些什么(潮子語錄),因為相比NOIP/CSP這個比賽其實比較簡單。

  在這里我不概括題意,因為現在有重現賽,所有人都可以看題。

A 牛妹愛整除

  首先,你知道為什么10進制下%3具有如此美妙的性質嗎?

  用B進制來表示一個數:$\sum_{i=0}^{\infin}a_iB^i$,我們希望:$(\sum_{i=0}^{\infin}a_iB^i)\%p=(\sum_{i=0}^{\infin}a_i)\%p$,因為 $a_i$ 是任意的,我們不能依賴它的性質,那唯一的方法就是讓 $B$ 的任意次方 $\%p$ 都等於1了。可以發現,當且僅當 $B\%p=1$ ,這個式子才是成立的。對於這道題,我們直接輸出k+1就可以了;事實上,只要不超過題目的最大限制,任意的 $xk+1$ 都可以。

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # define R register int
 4 
 5 using namespace std;
 6 
 7 int k;
 8 
 9 int main()
10 {
11     scanf("%d",&k);
12     printf("%d",k+1);
13     return 0;
14 }
A

B 吃桃

  從某種意義上來講,這個是不是比A還簡單?

  設 $f[i]$ 表示 $i$ 節點向子樹內能延伸的最長路徑,一遍dfs找到最長的根路徑...然后再dfs一遍輸出,就沒了...

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # define R register int
 4 
 5 using namespace std;
 6 
 7 const int N=100005;
 8 int n,k,h,x,y,firs[N],dp[N],dep[N];
 9 struct edge { int too,nex; }g[N<<1];
10 
11 void add (int x,int y)
12 {
13     g[++h].nex=firs[x];
14     firs[x]=h;
15     g[h].too=y;
16 }
17 
18 void dfs (int x)
19 {
20     int j;
21     for (R i=firs[x];i;i=g[i].nex)
22     {
23         j=g[i].too;
24         if(dep[j]) continue;
25         dep[j]=dep[x]+1;
26         dfs(j);
27         dp[x]=max(dp[x],dp[j]);
28     }
29     dp[x]++;
30 }
31 
32 void redfs (int x)
33 {
34     printf("%d\n",x);
35     if(dp[x]==1) return;
36     int j,ans=n+10;
37     for (R i=firs[x];i;i=g[i].nex)
38     {
39         j=g[i].too;
40         if(dep[j]<dep[x]) continue;
41         if(dp[j]+1==dp[x]) ans=min(ans,j);
42     }
43     redfs(ans);
44 }
45 
46 int main()
47 {
48     scanf("%d%d",&n,&k);
49     for (R i=1;i<n;++i)
50     {
51         scanf("%d%d",&x,&y);
52         add(x,y); add(y,x);
53     }
54     dep[k]=1; dfs(k);
55     redfs(k);
56     return 0;
57 }
B

C 背包

  眾所周知,一道題如果在題目名稱里明示某種算法,那它往往不是這種算法。不過...這道題就是背包;

  兩種方法:dp[i]表示大小為i,價值最小是多少;dp[i]表示價值為i,體積最大是多少;

  顯然第二種比較快,但是眾所周知牛客的機器非常神,所以第一種做法也能過(事實上我看了一眼直接背包的復雜度是2e8,感覺牛客2e8穩過,就沒有再進行任何思考了);最后補充一下,如果你也想嘗試第一種方法,也許需要在轉移時看一下放入這個物品后背包體積是否超過 $V$,這樣數組只要開到 $V$;

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # define R register int
 4 
 5 using namespace std;
 6 
 7 const int N=205;
 8 const int inf=1000000000;
 9 int n,V,v[N],w[N],dp[1000006];
10 int ans=inf;
11 
12 int main()
13 {
14     scanf("%d%d",&n,&V);
15     for (R i=1;i<=n;++i)
16         scanf("%d%d",&v[i],&w[i]);
17     for (R i=1;i<=V;++i) dp[i]=inf;
18     for (R i=1;i<=n;++i)
19     {
20         for (R j=V-v[i];j<V;++j)
21             ans=min(ans,dp[j]+w[i]);
22         if(v[i]>=V) ans=min(ans,w[i]);
23         for (R j=V;j>=v[i];--j)
24             dp[j]=min(dp[j],dp[ j-v[i] ]+w[i]);
25     }
26     ans=min(ans,dp[V]);
27     printf("%d",ans);
28     return 0;
29 }
C

D 泡面

  賽后zutter_告訴我我的做法可能假了,因為以前CF考過類似的題,當時被Hack了好多人;然后,最后的結論是這道題和CF的那個確實不是一個做法,所以我並沒有假;

  首先我們把所有人按照 $t_i$ 從小到大排序;然后模擬接水的過程就好了...具體來講,首先,維護一個值表示“當前接水的人什么時候能接完”,然后把所有 $t_i$ 小於這個數且還沒有接水的人的編號扔進一個小根堆里;每次取出堆頂作為新的接水人;如果堆空了,那就說明有一段時間並沒有人接水,我們直接找到下一個還沒有接過水的人 $x$,將“當前接水的人什么時候能接完”修改為 $t_x+p$ 即可。

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # include <algorithm>
 4 # include <queue>
 5 # define R register int
 6 # define ll long long
 7 
 8 using namespace std;
 9 
10 const int N=100005;
11 int n,p,h,s;
12 ll ans[N],T;
13 struct peo
14 {
15     int pos,t;
16 }a[N];
17 priority_queue <int,vector<int>,greater<int> > q;
18 
19 bool cmp (peo a,peo b)
20 {
21     if(a.t==b.t) return a.pos<b.pos;
22     return a.t<b.t;
23 }
24 
25 int read()
26 {
27     int x=0;
28     char c=getchar();
29     while (!isdigit(c)) c=getchar();
30     while (isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
31     return x;
32 }
33 
34 int main()
35 {
36     scanf("%d%d",&n,&p);
37     for (R i=1;i<=n;++i)
38         a[i].pos=i,a[i].t=read();
39     sort(a+1,a+1+n,cmp);
40     a[n+1].t=1000000005;
41     h=1; T=a[1].t+p; ans[ a[1].pos ]=T; 
42     int cg=1;
43     while(cg!=n)
44     {
45         while(h+1<=n&&a[h+1].t<=T) q.push(a[h+1].pos),h++;
46         int s=q.size();
47         if(s==0)
48         {
49             h++;
50             ans[ a[h].pos ]=a[h].t+p;
51             cg++;
52             T=a[h].t+p;
53             continue;
54         }
55         int x=q.top(); q.pop();
56         T+=p; ans[x]=T; cg++;
57     }
58     for (R i=1;i<=n;++i) printf("%lld ",ans[i]);
59     return 0;
60 }
D

E 偽直徑

  這道題具有較強的欺騙性;一開始我沒想明白,仿照求直徑的樹形dp設了兩個狀態:已經分叉過的路徑和沒有分叉過的路徑;轉移稍微有點復雜,但想起來很簡單;就在我馬上寫完的時候,我突然想起兩個路徑可以互相包含...

  好的,先說結論,這道題的答案就是樹的直徑-1;分兩步來證明:首先,這個值一定能取到,方法是一條路徑選直徑,另一條路徑去掉直徑端點處的某一條邊;其次,這確實是可能存在的最大值,因為如果存在兩條路徑的交是直徑,且他們不相同,那么取出它們不相交的部分接到直徑上,會使直徑變長,這顯然不合理,所以答案就是樹的直徑-1;

  
 1 # include <cstdio>
 2 # include <iostream>
 3 # define R register int
 4 
 5 using namespace std;
 6 
 7 const int N=200005;
 8 const int inf=1000000000;
 9 int n,h,x,y,firs[N],dep[N];
10 int f1[N],f2[N],e[N];
11 struct edge { int too,nex; }g[N<<1];
12 
13 void add (int x,int y)
14 {
15     g[++h].nex=firs[x];
16     firs[x]=h;
17     g[h].too=y;
18 }
19 
20 void dfs (int x)
21 {
22     int j;
23     for (R i=firs[x];i;i=g[i].nex)
24     {
25         j=g[i].too;
26         if(dep[j]) continue;
27         dep[j]=dep[x]+1;
28         dfs(j);
29         if(f1[j]+1>=f1[x]) f2[x]=f1[x],f1[x]=f1[j]+1,e[x]=j;
30         else f2[x]=max(f2[x],f1[j]+1);
31     }
32 }
33 
34 void redfs (int x)
35 {
36     int j;
37     for (R i=firs[x];i;i=g[i].nex)
38     {
39         j=g[i].too;
40         if(dep[j]<dep[x]) continue;
41         int t;
42         if(e[x]==j) t=f2[x]+1;
43         else t=f1[x]+1;
44         if(t>=f1[j]) f2[j]=f1[j],f1[j]=t,e[j]=x;
45         else f2[j]=max(f2[j],t); 
46         redfs(j);
47     }
48 }
49 
50 int main()
51 {
52     int ans=0;
53     scanf("%d",&n);
54     for (R i=1;i<n;++i)
55     {
56         scanf("%d%d",&x,&y);
57         add(x,y); add(y,x);
58     }
59     dep[1]=1; dfs(1);
60     redfs(1);
61     for (R i=1;i<=n;++i) ans=max(ans,f1[i]+f2[i]);
62     printf("%d",ans-1);
63     return 0;
64 }
E

F 最大最小差

  一道有點難度的題;

  首先考慮一個暴力?$n^2$枚舉區間,然后判斷是否合理,不知道能得多少分?如果你認真思考一下這個過程,會發現很多枚舉是無效的,舉個例子:如果$(x,y)$的利潤已經過高,那么$(x,y+1)$ 的利潤只可能更高;再想一想,就會發現對於一個確定的 $x$ ,合法的右端點屬於一個區間;我們是否可以通過某種方法直接找到這個區間呢?二分!怎么判斷一個區間的利潤呢?ST表查最大最小值!不過如果你每次同時處理 $m$ 種貨物,$ST$表的空間可能就太大了,所以可以考慮對於每種貨物分別計算合法的右端點,最后再對 $m$ 個區間求交;

  交上去,可能會TLE...因為ST表查詢一次雖說是O(n)的,但是常數略大。有沒有什么別的方法呢?發現隨着枚舉的做左端點不斷增大,右端點的合法區間也是在不斷右移的,所以可以用雙指針維護這個合法區間,復雜度雖說還是 $O(n\log n)$的,但是這個 $\log n$ 只是求 $ST$表時用到的,所以就可以通過了。

  賽后發現,這個最大最小值似乎可以通過單調隊列維護,那么復雜度就是嚴格的 $O(nm)$ 啦!

  啊,之前放上來的是一個WA+TLE的程序(因為我最后一次提交是在牛客提交框里改的,電腦上的還是原來那版錯誤的),經QQ上dalao提醒,現在已經改過來了。不過我的做法有點卡常,加上超級快讀才通過...

  
  1 # include <cstdio>
  2 # include <iostream>
  3 # define R register int
  4 # define ll long long
  5 # define getchar() (S==T&&(T=(S=BB)+fread(BB,1,1<<20,stdin),S==T)?EOF:*S++)
  6 char BB[1 << 20], *S = BB, *T = BB; 
  7   
  8 using namespace std;
  9 
 10 const int N=1000006;
 11 const int M=11;
 12 int n,m,a[N],c[M],lg[N];
 13 int st1[N][21],st2[N][21];
 14 int lr[N],rr[N];
 15 ll ans;
 16 
 17 void build_ST()
 18 {
 19     for (R i=1;i<=n;++i) st1[i][0]=st2[i][0]=a[i];
 20     for (R i=1;i<=20;++i)
 21         for (R j=1;j<=n;++j)
 22         {
 23             if(j+(1<<i)-1>n) break;
 24             st1[j][i]=min(st1[j][i-1],st1[j+(1<<(i-1))][i-1]);
 25             st2[j][i]=max(st2[j][i-1],st2[j+(1<<(i-1))][i-1]);
 26         }
 27 }
 28 
 29 int ask (int l,int r)
 30 {
 31     if(r>n) return -1;
 32     if(r<0) return -1;
 33     int k=lg[r-l+1];
 34     int ans1=min(st1[l][k],st1[ r-(1<<k)+1 ][k]);
 35     int ans2=max(st2[l][k],st2[ r-(1<<k)+1 ][k]);
 36     return ans2-ans1;
 37 }
 38 
 39 int ef1 (int id,int x)
 40 {
 41     int l=x,r=n,mid,ans=n+1;
 42     while(l<=r)
 43     {
 44         mid=(l+r)>>1;
 45         if(ask(x,mid)>=c[id]) ans=mid,r=mid-1;
 46         else l=mid+1;
 47     }
 48     return ans;
 49 }
 50 
 51 int ef2 (int id,int x)
 52 {
 53     int l=x,r=n,mid,ans=-n-1;
 54     while(l<=r)
 55     {
 56         mid=(l+r)>>1;
 57         if(ask(x,mid)<=c[id]) ans=mid,l=mid+1;
 58         else r=mid-1;
 59     }
 60     return ans;
 61 }
 62 
 63 int read()
 64 {
 65     int x=0;
 66     char c=getchar();
 67     while (!isdigit(c)) c=getchar();
 68     while (isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
 69     return x;
 70 }
 71 
 72 void solve (int id)
 73 {
 74     for (R i=1;i<=n;++i) a[i]=read();
 75     build_ST();
 76     int p1=ef1(id,1),p2=ef2(id,1);
 77     lr[1]=max(lr[1],p1); rr[1]=min(rr[1],p2);
 78     for (R i=2;i<=n;++i)
 79     {
 80         if(p1<i) p1=i; if(p2<i) p2=i;
 81         while(p1<=n&&ask(i,p1)<c[id]) p1++;
 82         while(p2+1<=n&&ask(i,p2+1)<=c[id]) p2++;
 83         if(ask(i,p1)!=c[id]) lr[i]=n+1; else lr[i]=max(lr[i],p1);
 84         if(ask(i,p2)!=c[id]) rr[i]=-n-1; else rr[i]=min(rr[i],p2);
 85     }
 86 }
 87 
 88 int main()
 89 {
 90     scanf("%d%d",&n,&m);
 91     for (R i=2;i<=n;++i) lg[i]=lg[i>>1]+1;
 92     for (R i=1;i<=m;++i) c[i]=read();
 93     for (R i=1;i<=n;++i) rr[i]=n;
 94     for (R i=1;i<=m;++i)
 95         solve(i);
 96     for (R i=1;i<=n;++i)
 97     {
 98         int l=lr[i],r=rr[i];
 99         if(l>r) continue;
100         ans+=r-l+1;
101     }
102     printf("%lld",ans);
103     return 0;
104 }
F

  由於對賽制不是很確定,以為類似CF,擔心會FST,又檢查來檢查去,后來問了監考老師,才知道過了就是過了,於是愉快的交卷。反正假都請好了,干脆在家度過了一個快樂的晚上。

  

Update:

  我! 沒! 了! 啊!

  這面試太太太太太太太難了吧!

  我! 自! 閉! 了!

  還沒面試的同學不要再問我面試考啥了,因為真真真真的很隨機啊啊啊啊啊!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM