# NPSC 2023 題目&詳解&sample code
[題本](https://contest.cc.ntu.edu.tw/npsc2023/teamclient/final-sen.pdf)
:::info
因為沒拿到code所以sample code是賽後重寫的
不一定與賽中做法相同
可能有錯
:::
## pA 非確定性義大利麵條醬汁料理
首先要知道
1. 平面上加直線會多 $1+與原有直線交點數量$ 個面
2. 期望值加法和乘常數可以亂亂做 (期望值是線性函數)
然後
一條直線對答案的貢獻可以訂為 $\frac 12(1+與原有直線交點數量期望值)$
交點數量期望值 = 交點出現概率總和
交點出現概率取決於有幾條原有直線經過
$n$條就是$1-\frac{1}{2^n}$
實作部分
每條直線只要考慮index比他小的
並用交點到題目給的向量兩端距離比代表交點位置
這可以用兩端點分別到另一向量兩端圍出的三角形面積比算
再用map存一存就好了
:::spoiler sample code (AC on TIOJ)
```cpp=
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 998244353;
const ll border = 1e6;
ll qpow(ll a, ll p){
ll r = 1;
while(p){
if (p&1) r=r*a%mod;
a=a*a%mod;
p>>=1;
}
return r;
}
ll rev(ll a){
return qpow(a,mod-2);
}
ll gcd(ll a, ll b){
while(a){
ll c = a;
a = b%a;
b = c;
}
return b;
}
struct pos{
ll x,y;
pos operator+(const pos &o) const{
return {x+o.x,y+o.y};
}
pos operator-(const pos &o) const{
return {x-o.x,y-o.y};
}
pos operator-() const{
return {-x,-y};
}
pos operator*(const ll &i) const{
return {x*i,y*i};
}
ll operator^(const pos &o) const{
return x*o.y-y*o.x;
}
bool out() const{
return abs(x)>border || abs(y)>border;
}
};
struct line{
pos a,b;
void extend(){
pos f1 = b-a;
pos f2 = a-b;
ll l = 0,r=1000000;
while(l<r){
ll m = l+r>>1;
if ((b+f1*m).out() && (a+f2*m).out()) r = m;
else l = m+1;
}
a = a+f2*l;
b = b+f1*l;
}
};
struct frac{
ll a,b;
frac():a(1),b(1){}
frac(ll x, ll y){
if(y<0) x = -x,y = -y;
ll g = gcd(abs(x),y);
a = x/g;
b = y/g;
}
bool operator==(const frac &o) const{
return a==o.a&&b==o.b;
}
bool operator!=(const frac &o) const{
return !(a==o.a&&b==o.b);
}
bool operator<(const frac &o) const{
return a!=o.a?a<o.a:b<o.b;
}
};
int main(){
ios::sync_with_stdio(0);cin.tie(0);
ll n;
cin >> n;
vector<line> a(n);
for(line &o : a) cin >> o.a.x >> o.a.y >> o.b.x >> o.b.y;
vector<ll> rev2(n,1);
ll r2 = rev(2);
for(ll i = 1;i<n;i++) rev2[i] = rev2[i-1]*r2%mod;
for(ll &i : rev2) i = (mod+1-i)%mod;
ll ans = 1;
for(ll i = 0;i<n;i++){
line cur = a[i];
map<frac,ll> mp;
for(ll j = 0;j<i;j++){
line o = a[j];
frac key(abs((o.b-cur.a)^(o.a-cur.a)),abs((o.b-cur.b)^(o.a-cur.b)));
mp[key]++;
}
ll x = 1;
for(auto [k,v] : mp) x = x+rev2[v];
ans = (ans+x%mod*r2)%mod;
}
cout << ans << "\n";
}
```
:::
## pB NPSC總統大選
觀察到
$1>\lfloor\frac12\rfloor$
所以給支持者都自己一個選區
其他給盡量少就好了
可以證明混合的選區必定不能使答案更好
:::spoiler sample code (AC on TIOJ)
```cpp=
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
ios::sync_with_stdio(0);cin.tie(0);
ll n,k;
cin >> n >> k;
vector<vector<ll>> v(k);
for(auto &o : v) o.push_back(0);
for(ll i=1;i<=n;i++){
ll a;
cin >> a;
v[a-1].push_back(i);
}
for(auto &o : v){
o.push_back(n+1);
ll x = o.size()-2;
ll y = 0;
for(ll i = 1;i<o.size();i++) y += o[i]>o[i-1]+1;
cout << (x>(x+y)/2?"Yes\n":"No\n");
}
}
```
:::
## pC 巴乙己放石頭問題
我不會數學
## pD 蛋餅愛爬山 2
對於一次查詢
一開始一定是選區間最高的點做起點最好
然後分別考慮第一步向左/右
先看向右
若$L_i$表$i$點開始第一步往左後自由亂跑的答案
$d_i$是$i$點可以往左反跳幾次
$x$是剛剛選到的最高點位置
答案是$max_{x\lt i\le qr}(d_i-d_x+L_i)$
這可以用線段樹之類的維護
為什麼可以這樣?
因為往右再往左之後不管怎樣跑都跑不出$(x,qr)$了
所以可以亂跑
一些細節
可跳躍的$(i,j)$最多只有$2n-2$個
因為可以到指定$j$的$i$只有左/右第一個比他高的
這關係可以用單調stack做
:::spoiler sample code (AC on TIOJ)
```cpp=
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
struct obj{
ll l,r,x;
};
struct segtree0{
ll n;
vector<pair<ll,ll>> a;
segtree0(ll N):n(N),a(N<<2){}
void set(ll i, ll l, ll r, ll p, ll x){
if(l == r) a[i] = {x,l};
else{
ll m = l+r>>1;
if (p<=m) set(i<<1,l,m,p,x);
else set(i<<1|1,m+1,r,p,x);
a[i] = max(a[i<<1],a[i<<1|1]);
}
}
pair<ll,ll> get(ll i, ll l, ll r, ll ql, ll qr){
if(ql<=l&&r<=qr) return a[i];
else{
ll m = l+r>>1;
if (qr<=m) return get(i<<1,l,m,ql,qr);
else if(ql>m) return get(i<<1|1,m+1,r,ql,qr);
else return max(get(i<<1,l,m,ql,qr),get(i<<1|1,m+1,r,ql,qr));
}
}
};
struct segtree{
ll n;
vector<ll> a;
segtree(ll N):n(N),a(N<<2){}
void set(ll i, ll l, ll r, ll p, ll x){
if(l == r) a[i] = x;
else{
ll m = l+r>>1;
if (p<=m) set(i<<1,l,m,p,x);
else set(i<<1|1,m+1,r,p,x);
a[i] = max(a[i<<1],a[i<<1|1]);
}
}
ll get(ll i, ll l, ll r, ll ql, ll qr){
if (ql>qr) return 0ll;
if(ql<=l&&r<=qr) return a[i];
else{
ll m = l+r>>1;
if (qr<=m) return get(i<<1,l,m,ql,qr);
else if(ql>m) return get(i<<1|1,m+1,r,ql,qr);
else return max(get(i<<1,l,m,ql,qr),get(i<<1|1,m+1,r,ql,qr));
}
}
};
int main(){
ios::sync_with_stdio(0);cin.tie(0);
ll n,q;
cin >> n >> q;
vector<ll> h(n);
for(ll &i : h) cin >> i;
vector<pair<ll,ll>> ord(n);
for(ll i = 0;i<n;i++) ord[i] = {h[i],i};
sort(ord.begin(),ord.end());
vector<obj> qs(q);
vector<ll> ans(q,0);
for(obj &o : qs) cin >> o.l >> o.r,o.l--,o.r--;
{
segtree0 t(n);
for(ll i = 0;i<n;i++) t.set(1,0,n-1,i,h[i]);
for(obj &o : qs) o.x = t.get(1,0,n-1,o.l,o.r).second;
}
vector<ll> pL(n,-1),pR(n,-1),scL(n,0),scR(n,0),sc(n,0);
{
vector<ll> v;
for(ll i = 0;i<n;i++){
while(v.size()&&h[v.back()]<h[i]) v.pop_back();
if (v.size()) pL[i] = v.back();
v.push_back(i);
}
}
{
vector<ll> v;
for(ll i = n-1;i>=0;i--){
while(v.size()&&h[v.back()]<h[i]) v.pop_back();
if (v.size()) pR[i] = v.back();
v.push_back(i);
}
}
for(ll i = 0;i<n;i++){
ll j = ord[i].second;
sc[j] = max(scL[j],scR[j]);
if(pL[j]!=-1) scR[pL[j]] = max(scR[pL[j]],sc[j]+1);
if(pR[j]!=-1) scL[pR[j]] = max(scL[pR[j]],sc[j]+1);
}
{
vector<ll> d(n,0);
for(ll i = n-1;i>=0;i--){
ll j = ord[i].second;
if(pL[j]!=-1) d[j] = d[pL[j]]+1;
}
segtree t(n);
for(ll i = 0;i<n;i++) t.set(1,0,n-1,i,d[i]+scL[i]);
for(ll i = 0;i<q;i++) ans[i] = max(ans[i],t.get(1,0,n-1,qs[i].x+1,qs[i].r)-d[qs[i].x]);
}
{
vector<ll> d(n,0);
for(ll i = n-1;i>=0;i--){
ll j = ord[i].second;
if(pR[j]!=-1) d[j] = d[pR[j]]+1;
}
segtree t(n);
for(ll i = 0;i<n;i++) t.set(1,0,n-1,i,d[i]+scR[i]);
for(ll i = 0;i<q;i++) ans[i] = max(ans[i],t.get(1,0,n-1,qs[i].l,qs[i].x-1)-d[qs[i].x]);
}
for(ll i : ans) cout << i << "\n";
}
```
:::
## pE 雞蛋糕
對於一個最短路徑
每一步走完後垂直剛剛的行動方向劃直線一定不會經過走過的點
因為走過的點一定離終點更遠
而這樣的點走最短路徑到目前點只能走直線
但若存在則與垂直的要求矛盾
所以若碰到燙的就翻轉這直線
一定能過
輸出"Yes"
:::spoiler sample code (AC on TIOJ)
```cpp=
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
ios::sync_with_stdio(0);cin.tie(0);
cout << "Yes\n";
}
```
:::
## pF 守護魔摩市
定義結果的特異點為剛到飽滿狀態或在飽滿狀態碰到魔法少女的點
然後對$i\in[1,N]$算"假設i是特異點,那碰到下個魔法少女後的下個特異點位置與之間的貢獻"
然後用倍增存碰到$2^n$個魔法少女後的數據
對每次查詢、$第一個魔法少女+A$ 為第一個特異點
用倍增找到不超出邊界的最後一個特異點
可能還能再碰一個魔法少女
但那樣多處理一下就好了
:::spoiler sample code
```cpp=
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
ios::sync_with_stdio(0);cin.tie(0);
ll n,m,a,b;
cin >> n >> m >> a >> b;
string s;
cin >> s;
vector<ll> v(n+1,n);
for(ll i = n-1;i>=0;i--){
v[i] = s[i]=='1'?i:v[i+1];
}
ll beg = a*(a+1)/2;
ll ed = a*b+a*(a-1)/2;
vector<vector<ll>> nxt(21,vector<ll>(n+1,n)),got(21,vector<ll>(n+1,0));
for(ll i = 0;i<n;i++){
ll g = s[i]=='1'?v[i+1]:v[i];
if(g==n){
nxt[0][i] = n;
got[0][i] = 0;
}else if (g<=i+b){
nxt[0][i] = g;
got[0][i] = (g-i)*a;
}else if (g<i+a+b){
ll d = g-i-b;
nxt[0][i] = g+d;
got[0][i] = a*b+d*(2*a-d);
}else{
nxt[0][i] = g+a;
got[0][i] = ed+beg;
}
nxt[0][i] = min(n,nxt[0][i]);
}
for(ll j = 1;j<21;j++){
for(ll i = 0;i<n;i++){
ll np = nxt[j-1][i];
nxt[j][i] = nxt[j-1][np];
got[j][i] = got[j-1][i]+got[j-1][np];
}
}
while(m--){
ll l,r;
cin >> l >> r;
l--,r--;
ll g = v[l];
if (g>r){
cout << "0\n";
continue;
}
ll ans = beg+ed;
ll cur = g+a;
for(ll j = 20;j>=0;j--){
if (nxt[j][cur]<=r){
ans += got[j][cur];
cur = nxt[j][cur];
}
}
if (cur<n&&(s[cur]=='1'?v[cur+1]:v[cur])<=r){
ans += got[0][cur];
}
cout << ans << "\n";
}
}
```
:::
## pG 猜餅乾
先query1~N
灰色表示不存在
然後query中位數
用這來限制$Q_1,Q_3$的位置
來一次query $Q_1,Q_3$
再不斷重複下去
:::spoiler sample code
```cpp=
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main(){
ios::sync_with_stdio(0);cin.tie(0);
ll n;
cin >> n;
cout << "?";
for(ll i = 1;i<=n;i++) cout << " " << i;
cout << endl;
vector<ll> a(n,-1);
string s;
cin >> s;
vector<ll> v;
for(ll i = 0;i<n;i++){
if (s[i]!='C') v.push_back(i+1);
}
vector<vector<ll>> b;
function<void(ll,ll,ll)> dfs;
dfs = [&](ll l, ll r, ll h){
if (l<r){
if (b.size()<=h) b.emplace_back();
ll m = l+r>>1;
b[h].push_back(v[m]);
dfs(l,m-1,h+1);
dfs(m+1,r,h+1);
}
};
dfs(0,v.size()-1,0);
for(auto &o : b){
vector<ll> q = a;
vector<ll> u = q;
vector<ll> d = q;
if (q[0]==-1) d[0] = v.front()-1;
for(ll i = 1;i<n;i++){
if(q[i]==-1) d[i] = d[i-1];
}
if (q[n-1]==-1) u[n-1] = v.back()+1;
for(ll i = n-2;i>=0;i--){
if(q[i]==-1) u[i] = u[i+1];
}
for(ll i = 0;i<n;i++){
if(q[i]==-1){
auto it = upper_bound(o.begin(),o.end(),d[i]);
if (it==o.end()||*it>=u[i]){
q[i] = *upper_bound(v.begin(),v.end(),d[i]);
}else q[i] = *it;
}
}
cout << "?";
for(ll i = 0;i<n;i++) cout << " " << q[i];
cout << endl;
string s;
cin >> s;
for(ll i = 0;i<n;i++){
if (s[i]=='A') a[i] = q[i];
}
}
{
vector<ll> q = a;
vector<ll> u = q;
vector<ll> d = q;
if (q[0]==-1) d[0] = v.front()-1;
for(ll i = 1;i<n;i++){
if(q[i]==-1) d[i] = d[i-1];
}
for(ll i = 0;i<n;i++){
if(q[i]==-1){
q[i] = *upper_bound(v.begin(),v.end(),d[i]);
}
}
cout << "!";
for(ll i = 0;i<n;i++) cout << " " << q[i];
cout << endl;
}
}
```
:::
## pH 巫醫單車
注意到限制有個奇妙的$NK\le4\times 10^6$
所以先算沒魔法時各事件結束時的cost
再$O(NK)$DP某一次事件若用過魔法且當前為$i$台車
:::spoiler sample code (AC on TIOJ)
```cpp=
#include<bits/stdc++.h>
using namespace std;
using ll = int; // long long 被卡常
ll k;
struct obj{
ll x,y,z,c;
bool operator<(const obj &o) const{
return z<o.z;
}
obj op(char o){
if (o=='1'){
if(c==0) return {x,y,z+1,c};
else return {x,y,z,c-1};
}else{
if(c==k) return {x,y,z+1,c};
else return {x,y,z,c+1};
}
}
};
int main(){
ios::sync_with_stdio(0);cin.tie(0);
ll n,b;
cin >> n >> k >> b;
string s;
cin >> s;
vector<obj> v(n+1);
v[0] = {1,b,0,b};
for(ll i = 0;i<n;i++) v[i+1] = v[i].op(s[i]);
vector<vector<obj>> dp(n,vector<obj>(k+1));
for(ll i = 0;i<n;i++)
for(ll j = 0;j<=k;j++)
dp[i][j] = {i+1,j,v[i].z,j};
for(ll i = 1;i<n;i++){
for(ll j = 0;j<=k;j++){
if (s[i-1]=='0'){
if (j>0) dp[i][j] = min(dp[i][j],dp[i-1][j-1].op(s[i-1]));
if (j==k) dp[i][j] = min(dp[i][j],dp[i-1][j].op(s[i-1]));
}else{
if (j<k) dp[i][j] = min(dp[i][j],dp[i-1][j+1].op(s[i-1]));
if (j==0) dp[i][j] = min(dp[i][j],dp[i-1][j].op(s[i-1]));
}
}
}
obj ans = dp[n-1][0].op(s[n-1]);
for(ll j = 0;j<=k;j++) ans = min(ans,dp[n-1][j].op(s[n-1]));
cout << ans.x << " " << ans.y << " " << ans.z << "\n";
}
```
:::