Namomo Summer Camp 23 Day 1 ABCDHI

发布时间 2023-08-24 01:20:53作者: nannan4128

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);
}