---
title: '2023/03/31 NYCU 2023 PCCA Week 07題單'
disqus: hackmd
---
###### tags:`競技程式設計一`
2023/03/31 NYCU 2023 PCCA Week 07題單
===
[TOC]
## Problem B Expeditious Cubing
### 解題關鍵 1. 浮點數運算
* 本題重點在於處理浮點數運算
* 由於浮點數判斷大於等於小於超級麻煩,加上本題有說明數字範圍都落在 20 內,所以採用「先以浮點數讀入,再轉以整數」的方式進行判斷大於等於小於的操作
* 假設現在給定浮點數 1.00 ,那我們在把 1.00 改成整數的時候只要寫 translate_num = 1.00*100 就好了嘛 ?
* 注意到電腦儲存浮點數 1.00 的方式可能是 1.00000001 或是 0.99999999999 之類的,後者很明顯跟我們想要的不同,那該怎麼做呢?
* 把 1.00 + 極小的 $eps$ 就可以解決問題啦
### 解題關鍵 2. 判斷解的情況
#### 無解
* 假設給定 $t_1, t_2, t_3, t_4$為由小到大排列的數字
* 加入 $t_4$ 後,最佳解的時間為 $\frac{t_1+t_2+t_3}{3}~$麻
* 如果這個解比給定的 $target$ 還要大,那就無解拉
#### 輸出無限大
* 假設給定 $t_1, t_2, t_3, t_4$ 為由小到大排列的數字
* 加入 $t_4$ 後,最爛解的時間為 $\frac{t_2+t_3+t_4}{3}~$麻
* 如果這個最爛解的時間比 target 還好,那顯然 $t_5$ 愛多大有多大麻
#### 輸出有限解
* 假設給定 $t_1, t_2, t_3, t_4$ 為由小到大排列的數字
* 觀察到 $t_5 \leq t_1$,因為比 $t_1$大也沒有意義(會被去除)
* 且 $t_5 > t_4$,因為比 $t_1$小也沒有意義(會被去除)
* 所以解題時間的平均必為 $\frac{t_2+t_3+t_5}{3}~$
### AC code
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(0);
#define pll pair<ll,ll>
#define F first
#define S second
#define eb emplace_back
#define mkp make_pair
const ll MAXN=1e6+5;
const ll INF=1e18;
const ll MOD=998244353;
const double eps=1e-6;
vector<ll> vec;
ll target;
double x;
void init(){
for(ll i=0;i<4;i++){
cin>>x;
x+=eps;
vec.push_back(x*100);
}
cin>>x;
x+=eps;
//存1.00可能被存成0.9999999或是1.00000001,加上eps可以避免這存取上的問題
target=x*100;//因為判斷整數相等比較容易,所以先轉成整數
sort(vec.begin(),vec.end());
}
void solve(){
if(vec[0]+vec[1]+vec[2]-3*target>0){
cout<<"impossible\n";
}
else if(vec[1]+vec[2]+vec[3]-3*target<=0){
cout<<"infinite\n";
}
else{
double ans=(double((3*target-vec[1]-vec[2]))+eps)/100;
cout<<fixed<<setprecision(2)<<ans<<'\n';
}
}
int main(){
fastio
// ll T;
// cin>>T;
// while(T--){
init();
solve();
// }
return 0;
}
```
## Problem DKayaking Trip
### 解題關鍵 1. 思考做法
* 這題看起來總船數 $m=\frac{b+n+e}{2}$ 很大,枚舉顯然 TLE
* 那要怎麼做會比較好呢 ?
* 觀察題目答案性質,ㄟ有單調性ㄟ
* 你問為啥答案有單調性?
* 因為假設最慢船隻可以達到船速 $v$ 好了,那真正最慢船隻的速度一定 $\geq v$麻
* 那就來試試看對答案二分搜阿
### 解題關鍵 2. 判斷二分搜的值是否合法
* 怎麼判斷當前的值 $val$,是否滿足會讓所有船速都 $\geq val$ 呢 ?
* 使用貪心的想法,我每次當前船隻配上划最慢的兩個人,但要滿足船速 $\geq val$ 這個限制
* 這個解會是最佳解
* 然後二分搜不要寫 $L,~R$ 的扣,用跳最大 $2$ 進位數字的扣,不用判斷左右界,超級好用
### 解題關鍵 3. 實作細節
* 在判斷兩個同為新手, 中手, 或老手的人是否可以一起組隊的時候,需要判斷新手, 中手, 或老手的人的數量是否 $\geq 2$,不能只有判斷 $>0$
### AC code
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(0);
#define pll pair<ll,ll>
#define F first
#define S second
#define eb emplace_back
#define mkp make_pair
const ll MAXN=1e6+5;
ll cnt[3],v[3],sum,c[MAXN],num[MAXN];
vector<pair<ll,pll>> vec;
void init(){
cin>>cnt[0]>>cnt[1]>>cnt[2];
cin>>v[0]>>v[1]>>v[2];
sum=(cnt[0]+cnt[1]+cnt[2])/2;
for(ll i=0;i<sum;i++) cin>>c[i];
sort(c,c+sum);
for(ll i=0;i<3;i++){
for(ll j=i;j<3;j++){
vec.eb(mkp(v[i]+v[j],mkp(i,j)));
}
}
sort(vec.begin(),vec.end());
}
void init2(){
for(ll i=0;i<3;i++){
num[i]=cnt[i];
}
}
bool ok(ll i,ll j,ll k,ll val){
if(k*(v[i]+v[j])>=val){
if(i==j){
if(num[i]>=2) return true;
}
else{
if(num[i]>0 && num[j]>0) return true;
}
}
return false;
}
bool isValid(ll val){
init2();
for(ll i=0;i<sum;i++){
bool flag=false;
for(auto it:vec){
if(ok(it.S.F,it.S.S,c[i],val)){
num[it.S.F]--;
num[it.S.S]--;
flag=true;
break;
}
}
if(!flag){
return false;
}
}
return true;
}
void solve(){
ll ans=0,step=pow(2,60);
while(step){
if(isValid(ans+step)){
ans+=step;
}
else{
step/=2;
}
}
cout<<ans<<'\n';
}
int main(){
fastio
init();
solve();
}
```
## Problem E Counting Subsequences (Hard)
### 解題關鍵 1. 計算區間和
* 怎麼計算 $S_{i,j}=a_i+a_{i+1}+\cdots+a_j$ 呢?
* 可以利用 $pre[i]$ 紀錄 $S_k=a_0+a_1+\cdots+a_k$ 的值
* 接著我們有 $S_{i,j}=a_i+a_{i+1}+\cdots+a_j=(a_0+a_1+\cdots+a_j)-(a_0+a_1+\cdots+a_{i-1})$
* 得到$S_{i,j}=S_j-S_{i-1}$,這個我們可以利用 $pre[j]-pre[i-1]$,$O(1)$時間查詢麻
### 解題關鍵 2. 觀察題目性質
* 當我們固定連續區間和的結尾在 $a[k]$ 時,我們想問有多少個這樣的連續區間和為 47 ,相當於問有多少個 $i$ 使得 $a_i+a_{i+1}+\cdots+a_k=pre[k]-pre[i-1]=47$ 麻
* 移項後我們想問有多少個 $i$ 滿足 $pre[i-1]=pre[k]-47$ 麻
* 那就用 STL 中的 $map$ 去紀錄 $pre[i-1]$ 就好了啊
### 解題關鍵 3. 注意題目邊界 case
* 注意到 0 0 47 這筆測資會讓上面的解不能過
* 這是為什麼呢 ? ~~因為我在假解~~
* 因為我們在記錄 $pre[i-1]$ 的時候,是用 for loop 從 $i$ 跑到 $N-1$ 麻
* 接著注意到在算 $mp[pre[k]-47]$,相當於問我在前面可以從索引 $0$ 的位置開始,要連續丟掉多少個 $arr[i]$ ,才可以讓區間和變成 47 的可行解有多少個 ,但這忽略了什麼都不丟掉的 case 啊 !
* 那怎麼做 ? 就把 $mp[0]+=1$ 啊
* 因為沒有東西相加的總和 = 0 麻
### AC code
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(0);
#define pll pair<ll,ll>
#define F first
#define S second
#define eb emplace_back
#define mkp make_pair
const ll MAXN=1e6+5;
const ll INF=1e18;
const ll MOD=998244353;
ll N,ans,arr[MAXN];
ll pre[MAXN];
map<ll,ll> mp;
void init(){
mp.clear();
mp[0]=1;
//要使得可以出現長度為1的子序列和,所以要設為1
//什麼都不加的總和是0麻
ans=0;
cin>>N;
for(ll i=0;i<N;i++){
cin>>arr[i];
if(i==0){
pre[i]=arr[i];
}
else{
pre[i]=arr[i]+pre[i-1];
}
}
}
void solve(){
for(ll i=0;i<N;i++){
mp[pre[i]]++;
ans+=mp[pre[i]-47];
}
cout<<ans<<'\n';
}
int main(){
fastio
ll T;
cin>>T;
while(T--){
init();
solve();
}
return 0;
}
```
## Problem F Fenwick Tree
### 解題關鍵 1. 選擇使用的資料結構
* 題目都說這是 $Fenwick Tree$
* 操作又是單點修改、區間查詢
* 那就直接 $BIT$ 砸下去啊
* ~~不要跟我一樣模板抄錯就好~~
### AC code
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(0);
#define pll pair<ll,ll>
#define F first
#define S second
#define eb emplace_back
#define mkp make_pair
const ll MAXN=1e7+5;
ll N,Q;
char c;
ll a,b,arr[MAXN];
void upd(ll id,ll x){
for(ll i=id;i<=N;i+=i&-i) arr[i]+=x;
}
ll sum(ll id){
ll res=0;
for(ll i=id;i>0;i-=i&-i) res+=arr[i];
return res;
}
void init(){
cin>>N>>Q;
}
void solve(){
while(Q--){
cin>>c;
if(c=='+'){
cin>>a>>b;
upd(a+1,b);
}
else{
cin>>a;
cout<<sum(a)<<'\n';
}
}
}
int main(){
fastio
// ll T;
// cin>>T;
// while(T--){
init();
solve();
// }
return 0;
}
```
## Problem G Supercomputer
### 解題關鍵 1. 抓取題目重點
* 題目有兩種操作
* 第一種操作 $F$ $i$
* 代表如果 $a[i]=1$,要把 $a[i]$ 設成 0
* 如果 $a[i]=0$,要把 $a[i]$ 設成 1
* 第二種操作 $F$ $l$ $r$
* 詢問 $[l,r]$ 間 1 的數量
* 第二種操作相當於詢問 $[l,r]$ 區間和
* 總結下來,第一種操作為單點改值,第二種操作為區間查詢
* 那就開 $BIT$ 阿
### 解題關鍵 2. 判斷 a[i] 是否 = 1
* $a[i]=S_i-S_{i-1}=query(i)-query(i-1)$
* 這個操作可以在 $O(log~n)$ 時間內完成
### AC code
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(0);
#define pll pair<ll,ll>
#define F first
#define S second
#define eb emplace_back
#define mkp make_pair
const ll MAXN=1e7+5;
ll N,Q;
char c;
ll a,b,arr[MAXN];
void upd(ll id,ll x){
for(ll i=id;i<=N;i+=i&-i) arr[i]+=x;
}
ll sum(ll id){
ll res=0;
for(ll i=id;i>0;i-=i&-i) res+=arr[i];
return res;
}
void init(){
cin>>N>>Q;
}
void solve(){
while(Q--){
cin>>c;
if(c=='F'){
cin>>a;
if(sum(a)-sum(a-1)==0)
upd(a,1);
else
upd(a,-1);
}
else{
cin>>a>>b;
cout<<sum(b)-sum(a-1)<<'\n';
}
}
}
int main(){
fastio
// ll T;
// cin>>T;
// while(T--){
init();
solve();
// }
return 0;
}
```
## Problem H Just for Sidekicks
### 解題關鍵 1. 觀察題目性質
* 第一筆操作等價於單點改值
* 第三筆操作等價於區間查詢
* 那感覺就要用 $BIT$ 了阿
* 可是只開一顆 $BIT$ 紀錄當前節點是哪種寶石,第二筆操作就會爛掉阿
* 簡單,那就開六棵 $BIT$ 去紀錄每種寶石的數量
### AC code
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define fastio ios::sync_with_stdio(false),cin.tie(0);
#define pll pair<ll,ll>
#define F first
#define S second
#define eb emplace_back
#define mkp make_pair
const ll MAXN=1e7+5;
ll N,Q,c;
ll a,b,bit[MAXN][7],v[7],arr[MAXN];
void upd(ll id,ll x,ll k){
for(ll i=id;i<=N;i+=i&-i) bit[i][k]+=x;
}
ll query(ll id,ll k){
ll res=0;
for(ll i=id;i>0;i-=i&-i) res+=bit[i][k];
return res;
}
void init(){
cin>>N>>Q;
for(ll i=1;i<=6;i++) cin>>v[i];
string s;
cin>>s;
for(ll i=1;i<=N;i++){
arr[i]=int(s[i-1]-'0');
upd(i,1,arr[i]);
}
}
void solve(){
while(Q--){
cin>>a>>b>>c;
if(a==1){
ll now=arr[b];
upd(b,-1,now);
upd(b,1,c);
arr[b]=c;
}
else if(a==2){
v[b]=c;
}
else{
ll ans=0;
for(ll i=1;i<=6;i++){
ans+=v[i]*(query(c,i)-query(b-1,i));
}
cout<<ans<<'\n';
}
}
}
int main(){
fastio
// ll T;
// cin>>T;
// while(T--){
init();
solve();
// }
return 0;
}
```