2020 CCPC河南省賽
A - 班委競選
簽到不多說
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
vector<pair<int,int>>v[N];
bool cmp(pair<int,int>a,pair<int,int>b){
if(a.first == b.first)
return a.second < b.second;
return a.first > b.first;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
int n,m; cin>>n>>m;
set<int>s;
for(int i = 1;i <= n; i++)
{
int c,t;
cin>>c>>t;
s.insert(c);
v[c].push_back({t,i});
}
for(auto i : s)
{
sort(v[i].begin(),v[i].end(),cmp);
cout<<v[i][0].second<<" ";
}
return 0;
}
B. 廣告投放
很容易想到是DP,但是發現複雜度太高了,怎麼辦呢?
首先我們定義\(dp[i][j]\)為前\(i\)集,有\(j\)個人時候的最大收益。
如果在第\(i\)集投放廣告,那麼第\(i+1\)集的收益:\(dp[i+1][j/d[i]]= max(dp[i+1][j/d[i]],dp[i][j]+j*p[i])\)
如果在第\(i\)集不投放廣告,那麼第\(i+1\)集的收益:\(dp[i+1][j] = max(dp[i+1][j],dp[i][j])\)
這樣看起來就像是一個由\(j/d[i]\)轉移的揹包了。
我們觀察一下,第\(i+1\)層都是由第\(i\)層,即它的上一層得到的,我們可以考慮滾動陣列來減少一維。考慮完MLE的問題我們要考慮TLE的問題了。同時列舉完所有的\(i,j\)肯定是時間複雜度爆炸的。但是我們知道是由\(j/d[i]\)轉移的。意味著,假設\(m\)能轉移,但是\(m-1,m-2\)這些是不能的,因為下一個也是\(m/2,m/3\)這些,中間很多是沒有意義的。而\(j\rightarrow \lfloor j/d[i]\rfloor\rightarrow \lfloor \lfloor j/d[i]\rfloor/d[i+1] \rfloor=\lfloor j/(d[i]*d[i+1])\rfloor\)。我們可以先預處理出第二維\(j\)的所有可能取值\(\lfloor m/i\rfloor\)(\(i\in[1,m]\)),而\(\lfloor m/i\rfloor\)只有\(\sqrt{m}\)種。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
ll p[N],d[N],dp[N];
map<ll,ll>mp;
ll n,m,a[N];
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n>>m;
for(int i = 1;i <= n; i++)
cin>>p[i];
for(int i = 1;i <= n; i++)
cin>>d[i];
int idx = 0;
a[++idx] = 0;
for(int i = m;i >= 1;i--)
{
if(!mp[m/i])
{
mp[m/i]++;
a[++idx] = m/i;
}
}
for(int i = 1;i <= n; i++)
{
for(int j = 1;j <= idx; j++)
{
dp[a[j]/d[i]] = max(dp[a[j]] + a[j]*p[i],dp[a[j]/d[i]]);
}
}
ll ans = -1e9;
for(int i = 0;i <= m; i++)
ans = max(ans,dp[i]);
cout<<ans<<"\n";
return 0;
}
C - 我得重新集結部隊
這個讀懂題就很容易啦,是個小模擬。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int n;
ll dist(ll x1,ll y1,ll x2,ll y2){
return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
bool f[N];
vector<array<ll,4>>c;
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n;
memset(f,true,sizeof(f));
for(int i = 1;i <= n; i++)
{
int op; cin>>op;
if(op == 1)
{
ll x,y,h; cin>>x>>y>>h;
c.push_back({x,y,h,i});
}else{
ll x1,y1,atk,r; cin>>x1>>y1>>atk>>r;
ll mindist = 1e18,idx = -1;
for(int j = 0;j < c.size(); j++){
if(f[c[j][3]] == false)continue;
ll x2 = c[j][0],y2 = c[j][1];
if(dist(x1,y1,x2,y2) < mindist){
idx = j;
mindist = dist(x1,y1,x2,y2);
}
}
if(idx == -1)continue;
ll x2 = c[idx][0],y2 = c[idx][1];
ll h = c[idx][2],evt = c[idx][3];
x1 = x2,y1 = y2;
for(int j = 0;j < c.size(); j++){
if(f[c[j][3]] == false)continue;
ll x2 = c[j][0],y2 = c[j][1];
ll h = c[j][2],evt = c[j][3];
ll d = dist(x1,y1,x2,y2);
if(d <= r*r){
h -= 3 * atk;
if(h > 0){
f[i] = false;
c[j][2] = h;
}
else f[evt] = false;
}
}
}
}
for(int i = 1;i <= n; i++){
if(f[i])
cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
E - 發通知 (離散化 + 差分)
題意:學院一共有 n 位學生,用 1, 2, ..., n 編號。每天,學院都會派遣輔導員給學生髮送若干通知,以保證各項措施、活動訊息得到落實。
現在,學院要求輔導員傳送一條關於光碟行動的通知。對於通知資訊,同學們的反應往往各不相同,輔導員預測出第$ i$ 號學生收到通知後會產生 \(w_i\) 的愉悅度。此外,輔導員還觀察到第 i 號學生會在$ [a_i, b_i] $時間段內實時查閱通知訊息,能夠收到這段時間內的所有通知;而其他時間將無法收到通知(愉悅度為 0)。
輔導員會選擇在某一時刻釋出一次通知訊息,他希望在至少有 k 名同學收到通知的前提下,使得同學們的總體愉悅度最大。同學們的總體愉悅度是所有同學愉悅度的異或和。請聰明的你幫助輔導員計算最大的總體愉悅度。
思路:關於區間操作,很容易會想到差分和字首和。但是我們發現\(a_i\)和\(b_i\)都很大呀,但是也沒有關係,離散化一下就好了。
對於區間\([l,r]\),我們在\(d[l]\)+=\(1\),\(d[r+1]\)-=\(1\)。同樣的\(t[l]\) ^= \(w\),\(t[r+1]\) ^= \(w\)。
離散化也是常操,unique一下。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 5e5 + 10;
ll a[N],b[N],w[N];
ll d[N*2],t[N*2];
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
int n,k; cin>>n>>k;
vector<ll>v;
for(int i = 1;i <= n; i++)
{
cin>>a[i]>>b[i]>>w[i];
v.push_back(a[i]);
v.push_back(b[i]+1);
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()))-v.end();
for(int i = 1 ; i <= n ; i ++)
{
int l = lower_bound(v.begin(), v.end(), a[i]) - v.begin() + 1;
int r = lower_bound(v.begin(), v.end(), b[i] + 1ll) - v.begin() + 1;
d[l] += 1ll, d[r] -= 1ll;
t[l] ^= w[i], t[r] ^= w[i];
}
ll ans = -1;
int len = v.size();
for(int i = 1 ; i <= len; i ++)
{
d[i] += d[i - 1];
t[i] ^= t[i - 1];
if(d[i] >= k)
ans = max(ans,t[i]);
}
cout<<ans<<"\n";
return 0;
}
當然你願意用map也是可以的,但是常數比較大
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
map<int,pair<int,int>>mp;
int main(){
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
int n,k; cin>>n>>k;
for(int i = 1;i <= n; i++)
{
int a,b,w; cin>>a>>b>>w;
mp[a].first ^= w;
mp[a].second += 1;
mp[b+1].first ^= w;
mp[b+1].second -= 1;
}
int hav = 0;
int ans = -1,now = 0;
for(auto x : mp){
now ^= x.second.first;
hav += x.second.second;
if(hav >= k)
ans = max(ans,now);
}
cout<<ans<<"\n";
return 0;
}
ps:布吉島為什麼,那麼容易的我寫了辣麼久emmm,果然是太久沒寫啦。
I. 太陽轟炸
思路:這個題其實很簡單。分情況討論。
if(a*n < h ),即所有都命中還是不能,答案就是0
else if(\(R_1+r \ge R_2\))那麼每次都能命中,答案是1
else{
有機率命中,有機率不命中,我們要算出機率。
\(R_3 = R_1+r\),在\(\le R_3\)可以命中。
命中的機率:\(p_1 = \dfrac{\pi R_3^2}{\pi R_2^2}=\dfrac{R_3^2}{R_2^2}\)
未命中的機率\(p_2 = 1-p_1 = \dfrac{R_2^2-R_3^2}{R_2^2}\)
接著算出至少需要命中的次數\(need = h/a + (h\%a!=0)\)。那麼命中\(\ge need\)都是可以的。
\(ans = \sum_{i = need}^{n}C_n^ip_1^ip_2^{n-i}\)
}
可以\(O(n)\)預處理出組合數和次冪。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 5e6 + 10;
ll fac[N],inv[N];
ll qmi(ll a, ll b, ll mod)
{
ll ans = 1 % mod;
while(b)
{
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
ll C(ll a,ll b)
{
return fac[a]*inv[b]%mod*inv[a-b]%mod;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
ll n,R1,R2,r,a,h; cin>>n>>R1>>R2>>r>>a>>h;
if(n*a < h)
{
cout<<0<<"\n";
return 0;
}
if(R2 <= R1+r){
cout<<1<<"\n";
}
else
{
fac[0]=1;
for(int i=1;i<=n+1;i++) fac[i]=fac[i-1]*i%mod;//預處理出階乘、階乘的逆元
inv[n+1]=qmi(fac[n+1],mod-2,mod);
for(int i=n;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
ll R3 = (R1+r)%mod;
ll t = qmi(R2,2,mod);
ll inv2 = qmi(t,mod-2,mod);
ll p1 = R3*R3%mod*inv2%mod;
ll p2 = ((R2*R2%mod - R3*R3%mod + mod)%mod)*inv2%mod;
if(p2 < 0)
p2 = (p2 + mod)%mod;
ll need = (h/a + (h % a != 0))%mod;
ll ans = 0;
for(int i = need;i <= n; i++)
{
ans = (ans + C(n,i)*qmi(p1,i,mod)%mod*qmi(p2,n-i,mod)%mod)%mod;
}
cout<<ans<<"\n";
}
return 0;
}
寫法2:
論我一開始為什麼T了(悲
每次用快速冪求逆元,組合數也沒用\(O(n)\)預處理,每次單獨求了(我是在幹什麼)。
T了之後改改如何過了。
// AC one more times
// nndbk
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
ll qmi(ll a, ll b, ll mod)
{
ll ans = 1 % mod;
while(b)
{
if(b & 1) ans = ans * a % mod;
a = a * a % mod;
b >>= 1;
}
return ans;
}
ll C(ll a, ll b, int p)
{
if (b > a) return 0;
ll res = 1;
for (int i = 1, j = a; i <= b; i ++, j -- )
{
res = (ll)res * j % p;
res = (ll)res * qmi(i, p - 2, p) % p;
}
return res;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
ll n,R1,R2,r,a,h; cin>>n>>R1>>R2>>r>>a>>h;
if(n*a < h)
{
cout<<0<<"\n";
return 0;
}
if(R2<=R1+r)
cout<<1<<"\n";
else
{
ll R3 = (R1+r)%mod;
ll t = qmi(R2,2,mod);
ll inv = qmi(t,mod-2,mod);
ll p1 = (((R3*R3)%mod)*inv)%mod;
ll p2 = ((R2*R2%mod - R3*R3%mod + mod)%mod)*inv%mod;
if(p2 < 0)
p2 = (p2 + mod)%mod;
ll need = (h/a + (h % a != 0))%mod;
ll ans = 0;
vector<ll>v,v2;
ll t1 = qmi(p1,need,mod),t2 = 1;
for(int i = need;i <= n; i++){
v.push_back(t1%mod);
t1 = t1*p1%mod;
v2.push_back(t2);
t2 = t2*p2%mod;
}
int l = 0,r = v2.size()-1;
ll fz = 1,fm = 1;
ll x = n,y = need;
for(int i = need;i >=1; i--)
{
fz = fz * x % mod;
fm = fm * y % mod;
x--;
y--;
};
for(ll i = need;i <= n; i++)
{
//ans = (ans + (((fz*qmi(fm,mod-2,mod))%mod*v[l++])%mod*v2[r--])%mod)%mod;
ll t = fz*qmi(fm,mod-2,mod)%mod;
t = t * v[l++] %mod;
t = t * v2[r--] %mod;
ans = (ans + t) % mod;
fz = fz*x%mod;
fm = ((fm*(i+1)%mod)%mod)%mod;
x--;
}
cout<<ans<<"\n";
}
return 0;
}