Namomo Summer Camp 23 Day 1
A - Amusement Arcade
题意:有\(n\)个座位,标号\(1\)到\(n\),每次进来一个人,除第一个人外,其他人进来坐的位置和其他以及坐下的人的位置尽量隔得远,问能不能实现所有人都坐在奇数位上。
思路:我们可以发现一个规律,当第一个人的位置确定了,其他人的位置也都是确定的。那么第一个人选了一个位置,这个人两边就转化为第一个人坐第一个位置(端点)的问题。对于这个第一次坐在第一个位置的子问题,我们再去考虑。如果第一个人坐在第一个,下一个肯定坐在另一个端点了,在下一个坐在它们的中点,以此类推,都是在中点位置。
于是,进一步观察,当第一个位置确定后,它两边都是2的幂次的时候是可行的。那么只用在\(\log^2\)的复杂度内暴力枚举就可以了。
#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 ans = 1;
while(b)
{
if(b & 1) ans = ans * a ;
a = a * a ;
b >>= 1;
}
return ans;
}
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
ll n;
cin>>n;
if(n==1||n==3){
cout<<1<<"\n";
return 0;
}
for(int i = 0;i<=90;i++)
{
for(int j = 0;j<=90;j++)
{
if(qmi(2,i)+qmi(2,j)+1==n)
{
cout<<qmi(2,i)+1<<"\n";
return 0;
}
}
}
cout<<"impossible\n";
}
B - Brexiting and Brentering
题意:找到最后一个元音\((a,e,i,o,u)\)的位置,后面的都切掉,在加上\(ntry\)即可。
思路:按题意模拟即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
string s;
cin>>s;
int len = s.size();
int last = -1;
for(int i = len-1;i>=0;i--)
{
if(s[i]=='a'||s[i]=='e'||s[i]=='i'||s[i]=='o'||s[i]=='u')
{
last = i;
break;
}
}
if(last == -1)
cout<<s<<"\n";
else {
for(int i = 0;i<=last;i++)
cout<<s[i];
cout<<"ntry\n";
}
return 0;
}
C - Card Trading
题意:告诉你\(n\)种报价和改报价的买入量和卖出量。当一个价格小于等于我们的报价,我们可以买入。当一个价格大于等于我们的报价我们可以卖出。问产生最大成交额的是哪一个,和最大成交额是多少?
思路:因为对于一个报价,小于等于它的我们可以买入,大于等于它的我们可以卖出。那么我们可以先以价格为第一关键字进行\(sort\),然后对一个报价的买入和卖出做一个前后缀处理即可。
注意:可能会有精度问题,我们先变成\(long\) $ long$,最后答案再变为小数。我是用字符串处理的,因为直接除以\(100\)还是会有精度问题\(wa\)了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int n;
vector<array<ll,3>>a;
ll buy[N],sale[N];
int main()
{
//ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n;
for(int i = 1;i <= n; i++)
{
double p;
ll x,y;
cin>>p>>x>>y;
// p*=100ll;
ll pp = p * 100ll;
a.push_back({pp,x,y});
}
a.push_back({0,0,0});
sort(a.begin(), a.end());
for(int i = 1;i <= n; i++)
{
buy[i] = buy[i-1]+a[i][2];
}
for(int i = n;i >= 1; i--)
{
sale[i] = sale[i+1]+a[i][1];
}
// for(int i = 1;i <= n;i++)
// cout<<buy[i]<<" "<<sale[i]<<"\n";
ll ans = 0,pos = -1;
for(int i = 1;i <= n;i++)
{
if(ans<min(buy[i],sale[i])*a[i][0])
ans = min(buy[i],sale[i])*a[i][0],pos = i;
}
if(pos==-1)cout<<"impossible\n";
else {
ll t1 = a[pos][0],t2 = ans;
string s1 = to_string(t1),s2 = to_string(t2);
int len1 = s1.size(),len2 = s2.size();
cout<<s1.substr(0,max(0,len1-2))<<"."<<s1.substr(len1-2,len1)<<" ";
cout<<s2.substr(0,max(0,len2-2))<<"."<<s2.substr(len2-2,len2)<<" ";
}
return 0;
}
/*
6
2.85 14 0
4.50 0 1
5.26 3 3
6.17 1 0
14.78 0 2
21.04 1 0
5
12.00 0 3
11.99 2 0
11.98 5 0
10.00 1 0
12.01 0 6
*/
D - Excursion to Porvoo
题意:有\(n\)个点和\(m\)条边,每条路有两个属性:距离和最大承重。当前能否走这条路,取决于当前重量是不是小于等于最大承重。\(q\)个询问,给你一个重量,问你从\(1\)到\(n\)的最短路,没有就输出\(impossible\)。
思路:一开始我的思路是,我考虑,对于大的承重加入承重小于它的边是没有意义的,所以我读入边之后,按照重量排序。然后询问也离线读入然后按重量从大到小排序。每次加的边是比当前询问重量大的边到之前加过的边为止。因为是从按照重量大到小排序的,那么之前可以用的边现在也一定可以用,只需要考虑多出来的边能不能更新以前的边即可。然后。。得到了一个\(TLE\)的写法。。。呜呜呜\(TAT\)。
以下是本人写的依托答辩:
#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
//vector<array<int,3>>e[N];
set<array<int,3>>weight[N];
set<int>s;
int ans[N];
vector<pair<int,int>>que;
int cnt,a[N];
int n,m,q;
int dist[N];
map<pair<int,int>,int>len;
bool cmp(pair<int,int> x,pair<int,int> y)
{
return x.first>y.first;
}
signed main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n>>m;
int maxc = 0;
vector<int>s;
for(int i = 1;i <= m;i++)
{
int x,d,c;
cin>>x>>d>>c;
if(len[{x,c}]==0)len[{x,c}] = 1e9;
len[{x,c}] = min(len[{x,c}],d);
// e[x].push_back({x+1,d,c});
maxc = max(maxc,c);
weight[c].insert({x,x+1,len[{x,c}]});
s.push_back(c);
}
sort(s.begin(),s.end());
s.erase(unique(s.begin(),s.end()),s.end());
for(auto x:s)
a[++cnt] = x;
cin>>q;
for(int i = 1;i<=q;i++)
{
int w;
cin>>w;
que.push_back({w,i});
}
sort(que.begin(), que.end(),cmp);
int last = cnt;
ll ret = 0;
int cnt = 0;
for(auto [w,i]:que)
{
if(w>maxc){
ans[i] = -1;
continue;
}
int pos = lower_bound(s.begin(),s.end(),w)-s.begin();
pos++;
//cout<<"pos = "<<pos<<" last = "<<last<<"\n";
for(int j = pos;j<=last;j++)
{
// cout<<"a[j] = "<<a[j]<<"\n";
for(auto [x,y,d]:weight[a[j]])
{
//cout<<"x = "<<x<<" y = "<<y<<" d = "<<d<<"\n";
int t = dist[x];
if(t==0||t>d)
{
dist[x] += (d-t),ret += (d-t);
if(t==0)cnt++;
}
}
}
last = pos;
if(cnt==n-1)
ans[i] = ret;
else ans[i] = -1;
}
for(int i = 1;i<=q;i++)
{
if(ans[i]==-1)cout<<"impossible\n";
else cout<<ans[i]<<"\n";
}
return 0;
}
好啦,那么正解是什么呢?与k同学一番讨论\((bushi)\)之后,以下是\(ac\)的思路:
首先,之前的写法问题出在询问部分,最坏的情况可能是\(n^2\)的了,因为没有单调性,所以新加入的边每个都要看一遍能不能更新之前的边,这里就是为什么会\(T\)。那怎么办呢?我们考虑对读入的边,按照重量从小到大排序。然后由于小的边可能不满足后面大的的情况,那么我们需要更新。用什么更新呢?当然是用满足当前条件并且距离最小的边去更新啦。那么我们预处理出来一个后缀数组\(suf[i][j]\),对于\(i->i+1\)的第\(j\)条边后面最优的边的位置(当然这个优化不加貌似也能过)。
之后就是读入询问(离线),用\(set\)存,也是自动按重量从小到大排序了。然后用\(edge\)数组存答案(还是用\(set\)),先全部\(insert(\lbrace 0,i\rbrace)\)表示当前\(i->i+1\)这条边承重是\(0\)。好的,开始处理询问啦。因为我们用\(set\)存的,那么\(edge\)的第一个就是当前承重最小的边,我们找到它,如果它是小于当前询问的考虑去更新,用一个\(last\)数组记录上次访问到哪里了,下次从它的下一个开始就行了(一个小优化)。不断更新(用满足当前重量的路径最短的那条边去更新),直到所有都满足即可,然后记录这个询问的答案。注意一下细节,藕调了好久QAQ。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int n,m,q;
vector<pair<int,int>>e[N];
set<pair<int,int>>que;
set<pair<int,int>>edge;
int last[N],ans[N];
int dist[N];
vector<int> suf[N];
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
cin>>n>>m;
for(int i = 1;i <= m; i++)
{
int x,d,c;
cin>>x>>d>>c;
e[x].push_back({c,d});
}
for(int i = 1;i < n ; i++)
sort(e[i].begin(),e[i].end());
for(int i = 1;i < n; i++)
{
int sz = e[i].size();
suf[i].resize(sz + 2);
suf[i][sz-1] = sz-1;
for(int j = sz-2;j>=0;j--)
{
int c = e[i][j].first, d = e[i][j].second;
int d2 = e[i][suf[i][j+1]].first,c2 = e[i][suf[i][j+1]].second;
if(d<d2&&c>=c2)suf[i][j] = j;
else suf[i][j] = suf[i][j+1];
}
}
// for(int i = 1;i < n; i++)
// {
// int sz = e[i].size();
// for(int j = 0;j<sz;j++)
// {
// cout<<suf[i][j]<<" ";
// }
// cout<<"\n";
// }
cin>>q;
for(int i = 1;i <= q; i++)
{
int w;
cin>>w;
que.insert({w,i});
}
for(int i = 1;i < n; i++)
{
edge.insert({0,i});
}
bool flag = false;
ll ret = 0;
for(auto [w,i]:que)
{
// cout<<"w = "<<w<<" i = "<<i<<"\n";
if(flag)
{
ans[i] = -1;
continue;
}
bool smaller = false;
bool ok = false;
while(edge.begin()->first<w)
{
// cout<<"edge.begin()->first = "<<edge.begin()->first<<"\n";
smaller = true;
int pos = edge.begin()->second;
int c = edge.begin()->first;
int sz = e[pos].size();
int n_c = c,n_d = 1e9;
int l_d = dist[pos];
ok = false;
// cout<<"pos = "<<pos<<" "<<"last[pos] = "<<last[pos]<<"\n";
// cout<<"c = "<<c<<" n_c = "<<n_c<<" n_d = "<<n_d<<" l_d = "<<l_d<<"\n";
// cout<<"sz = "<<sz<<"\n";
for(int j = last[pos];j<sz;j++)
{
//cout<<"e[pos][j].first = "<<e[pos][j].first<<" e[pos][j].second = "<<e[pos][j].second<<"\n";
if(e[pos][j].first>=w)
{
ok = true;
if(n_d>=e[pos][j].second)
n_c = e[pos][j].first,n_d = e[pos][j].second,last[pos] = j;
// cout<<" n_c = "<<n_c<<" n_d = "<<n_d<<"\n";
}
}
if(ok)
{
// cout<<" n_d = "<<n_d<<" l_d = "<<l_d<<"\n";
ret = ret + n_d - l_d;
ans[i] = ret;
//cout<<"ret = "<<ret<<"\n";
dist[pos] = n_d;
edge.erase(edge.begin());
edge.insert({n_c,pos});
}
else break;
}
if(smaller&&!ok){
ans[i] = -1;
flag = true;
}
if(!smaller)ans[i] = ret;
}
for(int i = 1;i <= q;i++)
{
if(ans[i]==-1)cout<<"impossible\n";
else cout<<ans[i]<<"\n";
}
return 0;
}
H - Looking for Waldo
题意:让你找到一个最小的矩形(平行于坐标轴)去框住包含\(WALDO\)所有字符的。输出该最小矩形面积,没有输出\(impossilbe\)。
思路:当时写的时候,kk提示我之前写个的一个\(abc\)的题,当然那个题很简单啦,题目是让你求一个最小的正方形,去框住数字,使得里面的和\(\ge k\),也是求它的面积。这个很容易,首先是正方形,那只需要知道一个点和边长就可以搞定了,数的和就用前缀和搞一搞。那那那其实本题就和那题差不多,不过改成了矩形,并且求的是有没有包含这\(5\)个字符。怎么办嘞。我们可以和那题一样考虑枚举一个左上角,然后枚举一条边,再去二分另一条边。那那那有没有包含这几个字符怎么办??好的,我这个笨蛋\(for\)了一遍暴力去找,显然是\(T\)飞了。那咋办捏。。还是前缀和呀,求一个你会,几个你就不会了?对这\(5\)个分开求就\(ok\)了,然后看是不是都有就行,搞定。
// AK one more times
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int n,m;
void solve()
{
cin>>n>>m;
int a[n+10][m+10];
int cnt[6][n+10][m+10];
for(int i = 1;i<=5;i++)
for(int j = 0;j<=n;j++)
for(int k = 0;k<=m;k++)
cnt[i][j][k] = 0,a[j][k] = 0;
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=m;j++)
{
char c;cin>>c;
if(c=='W')a[i][j] = 1;
else if(c=='A')a[i][j] = 2;
else if(c=='L')a[i][j] = 3;
else if(c=='D')a[i][j] = 4;
else if(c=='O')a[i][j] = 5;
}
}
for(int i = 1;i<=5;i++)
{
for(int j = 1;j<=n;j++)
{
for(int k = 1;k<=m;k++)
{
cnt[i][j][k] = cnt[i][j-1][k]+cnt[i][j][k-1]-cnt[i][j-1][k-1];
cnt[i][j][k] += (a[j][k]==i);
}
}
}
// for(int j = 1;j<=n;j++)
// {
// for(int k = 1;k<=m;k++)
// {
// cout<<a[j][k]<<" ";
// }
// cout<<endl;
// }
// cout<<endl;
// for(int i = 1;i<=5;i++)
// {
// for(int j = 1;j<=n;j++)
// {
// for(int k = 1;k<=m;k++)
// {
// cout<<cnt[i][j][k]<<" ";
// }
// cout<<endl;
// }
// cout<<endl;
// }
int ans = 1e9;
for(int x = 1;x<=n;x++)
{
for(int y = 1;y<=m;y++)
{
for(int w = 1;w<=n-x+1;w++)
{
int l = 1,r = m-y+1;
while(l<=r)
{
int mid = (l+r)>>1;
// cout<<"w = "<<w<<" h = "<<mid<<endl;
//bool ok1 = false,ok2 = false,ok3 = false,ok4 = false,ok5 = false;
// for(int i = x;i < x+w;i++)
// {
// for(int j = y;j < y+mid; j++)
// {
// if(a[i][j]=='W')ok1 = true;
// if(a[i][j]=='A')ok2 = true;
// if(a[i][j]=='L')ok3 = true;
// if(a[i][j]=='D')ok4 = true;
// if(a[i][j]=='O')ok5 = true;
// }
// }
int ok = 0;
for(int i = 1;i<= 5;i++)
{
if(cnt[i][x+w-1][y+mid-1]-cnt[i][x-1][y+mid-1]-cnt[i][x+w-1][y-1]+cnt[i][x-1][y-1])ok++;
}
if(ok==5){
r = mid-1;
// cout<<"r = "<<r<<endl;
ans = min(ans,(r+1)*w);
}
else l = mid+1;
}
}
}
}
if(ans==1e9)cout<<"impossible\n";
else
cout<<ans<<"\n";
}
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
solve();
return 0;
}
I - Monty's Hall
题意:有\(d\)个房间,门都是关着的,答案在其中一个房间,你不知道。然后呢,一开始你可以开\(s\)个门,主持人再开\(e\)个门,你可以考虑不变或者把你选的\(s\)个门换成别的关着的门。
思路:是个概率题,写完后看大家说是三门问题来的。考虑第一次中的概率:\(\dfrac{s}{d}\),那么没中的概率就是\(\dfrac{d-s}{d}\)。
再考虑我们要不要换。假设我们换\(l\)个,那么不换的就是\(s-l\)个。当然我们换的\(l\)个是不包括\(s,e\)的,那么能换的就是\(d-s-e\)个
最终概率就是:\(\dfrac{s}{d}\times \dfrac{s-d}{s}+\dfrac{d-s}{d}\times \dfrac{l}{d-s-e}\)。
我们只需要枚举\(l\)就\(ok\)啦,当然\(l\)的上界是\(min(s,d-s-e)\)。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 1e9 + 7;
const int N = 2e5 + 10;
int main()
{
ios::sync_with_stdio(false); cin.tie(nullptr), cout.tie(nullptr);
double d,s,e;
cin>>d>>s>>e;
double ans = 0;
for(int l = 0;l<=min(s,d-s-e);l++)
{
//cout<<(s-(double)l)/d<<" "<<((d-s)/d)*((double)l*(d-s-e))<<'\n';
ans = max(ans,((s-(double)l)/d)+((d-s)/d*((double)l/(d-s-e))));
}
printf("%.10lf\n",ans);
}