---
tags: DDJRC
---
# DDJ Regular Contest Round#4 "DD Jet" Tutorial
## a611: A. 栄、国士無双
<code>tags: treap, RMQ</code>
RMQ問題,一般來說可以用線段樹、BIT、或是treap來解
但這題的鍵值非整數,所以用treap比較方便
實作上要注意詢問是左閉右閉,所以用同一個`split`函數來處理可能會少掉一邊的邊界沒取到
這時候可以寫兩個`split`來解決 :poop:
:::spoiler code by *konchin.shih*
```cpp=
mt19937 mt;
struct node{
int a,b,mx,v;
node *l,*r;
node* update(){
mx=a;
if(l) mx=max(mx,l->mx);
if(r) mx=max(mx,r->mx);
return this;
}
node(int x,int y):
a(x),b(y),mx(x),v(mt()),l(nullptr),r(nullptr){}
};
using treap = node*;
treap merge(treap a,treap b){
if(!a||!b) return a?:b;
if(a->v<b->v)
return a->r=merge(a->r,b),a->update();
else
return b->l=merge(a,b->l),b->update();
}
void lower_split(treap t,int ka,int kb,treap& a,treap& b){
if(!t){a=b=nullptr;return;}
if(1LL*t->a*kb<1LL*t->b*ka)
a=t,lower_split(a->r,ka,kb,a->r,b),a->update();
else
b=t,lower_split(b->l,ka,kb,a,b->l),b->update();
}
void upper_split(treap t,int ka,int kb,treap& a,treap& b){
if(!t){a=b=nullptr;return;}
if(1LL*t->b*ka<1LL*t->a*kb)
b=t,upper_split(b->l,ka,kb,a,b->l),b->update();
else
a=t,upper_split(a->r,ka,kb,a->r,b),a->update();
}
void insert(treap& t,int x,int y){
treap l,r;
lower_split(t,x,y,l,r);
t=merge(merge(l,new node(x,y)),r);
}
int query(treap& t,int la,int lb,int ra,int rb){
treap l,m,r;
lower_split(t,la,lb,l,r);
upper_split(r,ra,rb,m,r);
int ret=m?m->mx:-1;
t=merge(merge(l,m),r);
return ret;
}
inline void solve() {
treap tree=nullptr;
int n;cin>>n;
while(n--){
int op;cin>>op;
if(op){
int la,lb,ra,rb;
cin>>la>>lb>>ra>>rb;
cout<<query(tree,la,lb,ra,rb)<<endl;
}else{
int a,b; cin>>a>>b;
insert(tree,a,b);
}
}
}
```
:::
---
## a597: B. DD的背包問題
<code>tags: DP, knapsack</code>
經典的分組背包問題,不過每個頻道只能選一種訂閱層級
所以轉移時必須從上一個頻道的最佳解進行轉移
:::spoiler code by *konchin.shih*
```cpp=
inline void solve() {
int n,c; cin>>n>>c;
vector<long long> dp(c+1,0);
while(n--){
static int k; cin>>k;
auto pre = dp;
while(k--){
static int v,w; cin>>v>>w;
for(int i=c;i>=w;i--)
dp[i]=max(dp[i],v+pre[i-w]);
}
}
cout<<dp.back()<<endl;
}
```
:::
---
## a605: C. 指揮官的背包問題
<code>tags: DP, knapsack</code>
也是背包問題,不過背包的容量(經驗值)太大無法用容量DP
再者,可以觀察到物品的價值總和(等級%數)最多只會有 $n\times 100$ 種
於是可以用價值作為DP的參數
具體來說就是對於每個價值,儲存最少需要使用多少容量
最後遍歷一次,在容量不超過 $exp$ 的情況下取最大價值就好
另外要注意輸入有浮點數,建議轉成整數再做運算
:::spoiler code by *konchin.shih*
```cpp=
using ll=long long;
inline void solve() {
ll n,exp; cin>>n>>exp;
vector<ll> dp(100001,1e15+5); dp[0]=0;
while(n--){
ll a,b; string str;
cin>>a>>str;
str.resize(str.find('.')+3,'0');
str.resize(remove(str.begin(),str.end(),'.')-str.begin());
sscanf(str.data(),"%lld",&b);
cout<<b<<endl;
for(int i=100000;i>=b;i--)
dp[i]=min(dp[i],a+dp[i-b]);
}
for(int i=100000;i>=0;i--)
if(dp[i]<=exp){
cout<<fixed<<setprecision(2)<<i/100.0<<endl;
return;
}
}
```
:::
---
## a607: D. 馬娘玩家的末路
<code>tags: convex hull</code>
這題就是求凸包面積,要注意的是題敘裡有提到**與原點**所構成的面積
所以要在點集裡面加上原點才會正確
:::spoiler code by *konchin.shih*
```cpp=
using ll=long long;
template<typename T> using V=vector<T>;
template<typename T1,typename T2=T1> using P=pair<T1,T2>;
inline void solve() {
int n; cin>>n;
V<P<ll>> v(n);
for(auto& i:v)
cin>>i.first>>i.second;
v.emplace_back(0,0),n++;
sort(v.begin(),v.end());
V<P<ll>> s{v[0],v[1]};
auto cross = [](P<ll> o,P<ll> a,P<ll> b){
return (a.first-o.first)*(b.second-o.second)
-(b.first-o.first)*(a.second-o.second);
};
auto revs=[&]{return s.rbegin();};
for(int i=2;i<n;s.emplace_back(v[i++]))
while(s.size()>=2&&cross(revs()[1],revs()[0],v[i])<=0)
s.pop_back();
for(int i=n-2,t=s.size()+1;i>=0;s.emplace_back(v[i--]))
while(int(s.size())>=t&&cross(revs()[1],revs()[0],v[i])<=0)
s.pop_back();
auto area=[](P<ll> a,P<ll> b){
return abs(a.first*b.second-a.second*b.first);
};
ll sum=0;
for(int i=1;i<int(s.size());i++)
sum+=area(s[i-1],s[i]);
ll maxn=0,k;
for(int i=0;i<11;i++)
cin>>k,maxn=max(maxn,k<<1);
if(maxn<sum)
cout<<"nice"<<endl;
else
cout<<"sleep"<<endl;
}
```
:::
---
## a609: E. Getting Over Orange
<code>tags: math, Gaussian elimination</code>
由題目可以推得以下公式
$t=$ 到終點的期望時間$$t_i=a_i(1+t_{i+1})+b_i(1+t_i)+c_i\sum^{k<i}_{k=0}(\frac{1+t_k}{i})$$
則可以化簡成$$a_it_{i+1}+(b_i-1)t_i+c_i\sum_{k=0}^{k<i}\frac{t_k}{i}=-a_i-b_i-c_i$$
又 $\because a_i+b_i+c_i=1$ $$\therefore\ a_it_{i+1}+(b_i-1)t_i+c_i\sum_{k=0}^{k<i}\frac{t_k}{i}=-1$$
可以發現最終可以列出 $n$ 條 $n$ 元一次方程式
則可以用高斯消去法求解
注意實作時需要利用模逆元求 $Q^{-1}$
也可以看個人習慣實作分數,取代頻繁的取反元素
:::spoiler code by *konchin.shih*
```cpp=
constexpr int mod = 1e9+7;
struct frac{
ll a,b;
frac(ll x=0,ll y=1):
a((x%mod+mod)%mod),b(y%mod){}
operator bool(){return bool(a);}
};
frac operator+(frac a,frac b){
return frac(a.a*b.b+a.b*b.a,a.b*b.b);
}
frac operator-(frac a){
return frac(mod-a.a%mod,a.b);
}
frac operator/(frac a,frac b){
return frac(a.a*b.b,a.b*b.a);
}
frac operator*(frac a,frac b){
return frac(a.a*b.a,a.b*b.b);
}
ll fpow(ll a,ll b){
ll ret=1;
for(a%=mod;b;b>>=1,a=a*a%mod)
if(b&1) ret=ret*a%mod;
return ret;
}
ll rev(ll a){return fpow(a,mod-2);}
template<typename T> using V=vector<T>;
template<typename T,int N> using A=array<T,N>;
inline void solve() {
int n;cin>>n;
V<V<frac>> dp(n+1,V<frac>(n+1,frac(0)));
V<A<frac,3>> v(n);
for(auto& i:v)
for(auto& j:i){
int k;cin>>k;
j=frac(k)/frac(100);
}
for(int i=0;i<n;i++){
dp[i][i]=-v[i][1]+frac(1);
dp[i][i+1]=-v[i][0];
for(int k=0;k<i;k++)
dp[i][k]=-v[i][2]/frac(i);
dp[i][n]=frac(1);
}
for(int i=0;i<n;i++){
for(int j=i+1;j<n&&!dp[i][i];j++)
dp[i].swap(dp[j]);
if(!dp[i][i])
continue;
for(int j=0;j<n;j++){
if(i==j) continue;
frac t=-dp[j][i]/dp[i][i];
for(int k=i;k<=n;k++)
dp[j][k]=dp[j][k]+t*dp[i][k];
}
}
frac ans=dp[0][n]/dp[0][0];
cout<<ans.a*rev(ans.b)%mod<<endl;
}
```
:::
### 另解
> inspired by *ub33*
如果把 $t$ 定義成從第 $i$ 個難關到第 $i+1$ 個難關的期望時間
就可以不用消元,達到 $O(n)$ 的複雜度
:::spoiler code by *konchin.shih*
```cpp=
constexpr int mod = 1e9+7;
inline ll rev(ll a,ll b=mod-2){
ll ret=1;
for(a%=mod;b;b>>=1,a=a*a%mod)
if(b&1) ret=ret*a%mod;
return ret;
}
inline void solve() {
int n;cin>>n;
ll a,b,c,prev=0,sum=0,cur;
for(int i=1;i<=n;i++){
cin>>a>>b>>c;
b=b*rev(100)%mod;
c=c*rev(100)%mod;
cur=(1+prev*rev(i-1)%mod*c)%mod;
cur=cur*100LL%mod*rev(a)%mod;
sum=(sum+cur)%mod;
prev=(prev+cur*i)%mod;
}
cout<<sum<<endl;
}
```
:::