ABC 250

E - Prefix Equality(Hash)

Problem

给定两个序列\(A=(a_1,...,a_N)\)\(B=(b_1,...,b_n)\)。现在有\(Q\)个询问,每个询问给定两个数字\(x_i,y_i\),如果\(A\)的前\(x_i\)个数组成的集合和\(B\)的前\(y_i\)个数组成的集合相等,那么输出\(Yes\),否则输出\(No\)\(1\le N,Q\le 2\times 10^5\)

Sol

哈希,注意哈希值范围开到unsigned long long,避免哈希冲突,比如一个集合里面数不同,但他们的集合哈希值相同。

Code

#include <bits/stdc++.h>
#define ll long long
#define ull unsigned long long
using namespace std;
mt19937_64 mrand(random_device{}());
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin>>n;
    map<int,ull>cnt;
    set<int>sa,sb;
    vector<ull>prea(n+1),preb(n+1);
    vector<int>a(n+1),b(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(!cnt.count(a[i])) cnt[a[i]]=mrand();
        prea[i]=prea[i-1];
        if(!sa.count(a[i])) prea[i]+=cnt[a[i]];
        sa.insert(a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        cin>>b[i];
        if(!cnt.count(b[i])) cnt[b[i]]=mrand();
        preb[i]=preb[i-1];
        if(!sb.count(b[i])) preb[i]+=cnt[b[i]];
        sb.insert(b[i]);
    }
    int q;
    cin>>q;
    while(q--)
    {
        int x,y;
        cin>>x>>y;
        if(prea[x]==preb[y]) cout<<"Yes\n";
        else cout<<"No\n";
    }
    return 0;

}

F - One Fourth(计算几何、叉积、前缀和、双指针)

Problem

你有一个\(N\)个点的凸多边形披萨,每个点有一个坐标\((X_i,Y_i)\),你可以选择两个不相邻的点,在这两个点之间连线,把披萨分为两部分,然后你可以选择一部分吃掉。现在记\(a\)为原来这块披萨面积的\(\frac{1}{4}\)\(b\)为你吃掉的披萨的面积。要求你计算最小的\(8|a-b|\)\(4\le N\le 10^5,|X_i|,|Y_i|\le 4\times 4\times 10^8\)

Sol

  • 凸多边形面积计算:对于一个凸多边形,其顶点按照逆时针依次是\((x_1,y_1),...,(x_n,y_n)\),则其面积\(S=\frac{1}{2}|\sum_{i=1}^{n-1}(x_iy_{i+1}-x_{i+1}y_i)-(x_0y_n-x_ny_0)|\)。由于是环状的,所以我们可以考虑把数组赋值一份到后面,这样就可以求出任意起点开始的多边形面积了,并且用前缀和优化一下即可。
  • 双指针:假设当前指针为\(l,r\),并且这部分的面积小于\(\frac{S}{4}\),并且\(l,r+1\)这部分的面积大于\(\frac{S}{4}\),那么答案可能在这两部分之间,因为去差的绝对值,所以这两部分是最接近\(\frac{S}{4}\)

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin>>n;
    vector<ll>x(2*n+3),y(2*n+3);
    for(int i=1;i<=n;i++)
        cin>>x[i]>>y[i];
    for(int i=1;i<=n;i++) x[i+n]=x[i],y[i+n]=y[i];
    vector<ll>s(2*n+3);
    for(int i=1;i<=2*n+1;i++) s[i+1]=s[i]+x[i]*y[i+1]-x[i+1]*y[i];
    ll S=s[n+1];
    ll ans=8e18;
    int r=1;
    auto cal=[&](int l,int r)->ll{

        return s[r]-s[l]+x[r]*y[l]-x[l]*y[r];
    };
    for(int l=1;l<=n;l++)
    {
        while(cal(l,r+1)*4<=S) r++;
        ans=min(ans,abs(S-cal(l,r)*4));
        ans=min(ans,abs(S-cal(l,r+1)*4));
    }
    cout<<ans<<'\n';
}

G - Stonks(模拟费用流、带悔贪心)

Problem

公司\(X\)在接下来的\(N\)天会进行股票贸易。你知道未来\(N\)天每天的股票价钱。每天可以进行一下 3 中操作之一:

  • 买入一股股票,花费\(P_i\)
  • 卖出一股股票,收益\(P_i\)
  • 什么都不做

你一开始有无限多的钱,但没有股票,请你计算可以获得的最大收益

Sol

一个很自然的想法:每次低价买入,高价卖出。但有个问题,就是我们遇到高价的时候,可能没有股票可以卖,因此我们要考虑之前最低价买入股票。于是可以有这样一个做法:对于第\(i\)天,先假设有股票卖出,把\(p_i\)加入答案中,然后把\(-p_i\)加入优先队列中,然后取出队头,加入答案中,因为当前是卖出股票,因此相当于前\(i\)天一定有一天买入了,所以选择最小的那个买入即可。注意\(-p_i\)要入队两次,为什么?因为我们其实每次卖出有两种选择:(1)在前\(i\)天卖出的某一天中选一天不卖,留给第\(i\)天卖;(2)在前\(i\)天中选择某一天买入。并且我们在第\(i\)天就先把\(-p_i\)加入队列再统计贡献,因为这一天不一定是最优的。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n;
    cin>>n;
    ll ans=0;
    priority_queue<int>q;
    for(int i=1;i<=n;i++)
    {
        int x;
        cin>>x;
        ans+=x;
        q.push(-x);
        q.push(-x);
        ans+=q.top();
        q.pop();

    }
   cout<<ans<<'\n';
}

学习链接 1
学习链接 2

Ex - Trespassing Takahashi(图论)

Problem

\(N\)个点和\(M\)条路,第\(i\)条路连接点\(a_i\)\(b_i\),走这条路需要花费\(c_i\)分钟。有\(K\)个房子,分别在点\(1,...,K\)上。
现在有\(Q\)个询问,每次询问给定\(x_i,y_i,t_i\)\(Takahashi\)在房子\(x_i\),想要到达房子\(y_i\),但他体力有限,每次走\(t_i\)分钟(不一定要走完),他都要需要睡觉来补充体力,而他只能在有有房子的点睡觉,问他是否可以到达点\(y_i\)

Sol

首先对于每个房子跑一次最短路,得到每个房子到各个点的最短路,并记录每个点距离最近的房子。然后对于原图的每条边的两个端点\(u,v\),如果这两个端点距离最近的房子不同,那么这两个房子\(pre_u,pre_v\)之间的经过\(u,v\)的最短路径就是\(pre_u\)的到\(u\)的最短路加上\(pre_v\)\(v\)的最短路再加上\(u,v\)之间的距离,把这样一条以\(pre_u,pre_v\)为端点,权值为\(dist_u+dist_v+w\)的路径记录起来,最后把所有这样的边按距离从小到大排序。询问时,每次把当前距离小于\(t_i\)的边的两个房子合并在一个集合里,说明\(Takahashi\)可以从这两个房子之间移动并回复体力。最后只要判断询问的两个点\(x_i,y_i\)是否在一个集合里即可,因为这个集合里的房子是一定可以通过花费小于\(t_i\)的时间相互到达。

Code

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n,m,k;
    cin>>n>>m>>k;
    vector<vector<pair<int,int>>>e(n+1);
    vector<array<int,3>>E(m+1);
    for(int i=1;i<=m;i++)
    {
       int u,v,w;
       cin>>u>>v>>w;
       E[i]={u,v,w};
       e[u].emplace_back(v,w);
       e[v].emplace_back(u,w);
    }
    vector<int>pre(n+1);
    vector<ll>dist(n+1,1e18);

    auto dijkstra=[&]()->void{
        vector<int>vis(n+1);
        priority_queue<pair<ll,int>>q;
         for(int i=1;i<=k;i++)
         {
            dist[i]=0;
            pre[i]=i;
            q.push({dist[i],i});
         }
         while(q.size())
         {
            auto [w_,u]=q.top();
            q.pop();
            if(vis[u]) continue;
            vis[u]=1;
            for(auto [v,w]:e[u])
            {
                if(dist[v]>dist[u]+w)
                {
                    dist[v]=dist[u]+w;
                    pre[v]=pre[u];
                    q.push({-dist[v],v});
                }
            }
         }
    };
    dijkstra();
    vector<array<ll,3>>ed;
    for(int i=1;i<=m;i++)
    {
        auto [u,v,w]=E[i];
        if(pre[u]!=pre[v]) ed.push_back({dist[u]+dist[v]+w,pre[u],pre[v]});
    }
    vector<int>f(n+1);
    for(int i=1;i<=n;i++) f[i]=i;
    auto find=[&](auto self,int x)->int{
        return f[x]==x?x:f[x]=self(self,f[x]);
    };
    sort(ed.begin(),ed.end());
    int q;
    cin>>q;
    int id=0;
    while(q--){
        int x,y;
        ll t;
        cin>>x>>y>>t;
        while(id<ed.size()&&ed[id][0]<=t){
            int u=ed[id][1],v=ed[id][2];
            f[find(find,u)]=find(find,v);
            ++id;
        }
        if(find(find,x)==find(find,y)) cout<<"Yes\n";
        else cout<<"No\n";
    }
    return 0;

}
posted @ 2022-05-09 20:29  Arashimu  阅读(31)  评论(0编辑  收藏  举报