acwing周赛部分题解

1、22-构造数组
解题思路:
acwing周赛部分题解_第1张图片
观察可以发现b数组始终是大于a数组的,而两个数组中的每个数都是从1-n中选择的,可以重复。
因此
第一步:从1-n中选择2m个数
第二部:将这2m个数排序,大的m个数给b,小的m个数给a ------一种方案
答案就是从1-n中选择2m个数有多少种选择方案。
转化一:设x1,x2,x3,…xn表示这n个数在一轮选择中被选中的次数,即有
x1+x2+x3+…+xn = 2m,那么这个不定方程解的个数就是答案
转化二:设x’i = xi+1;
x’1 + x’2 + … + x’n = 2m+n;
转化三:隔板法
这时可以处理成有2m+n个小球,用n-1个板子将其隔开,有多少种选法,答案是C2m+n-12m种

#include 
#include 
#include 
using namespace std;
typedef long long LL;
const int mod = 1e9+7;
const int N = 2010;
int n,m;
int c[N][N];
void init()
{
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j <= i; j ++ )
            if (!j) c[i][j] = 1;
            else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
int main()
{
    cin >> n >> m;
    init();
    cout << c[2*m+n-1][2*m] <<"\n";
    return 0;
}

2、13-最大路径权值
解题思路:拓扑排序、dp
(1)考虑正无穷的情况,那就是出现环了。有向无环图判断环就是拓扑排序的结果小于n
(2)根据拓扑排序结果从后往前推,f[i][j]表示从第i个点出发的所有路径中值为j的个数的最大值。
(3)遍历所有的节点,取max(f[i][j])

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int N = 3e5+10, M = 26;
int f[N][M];
char a[N];
vector<int> path; // 拓扑排序的结果
int n,m;
int h[N], e[N], ne[N], idx;
int din[N];
void add(int a,int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}
bool topo()
{
    queue<int> q;
    for(int i = 1; i <= n; i ++)
    {
        if(din[i] == 0) q.push(i);
    }
    int res = 0;
    while(q.size())
    {
        int t =q.front();
        q.pop();
        res ++;
        path.push_back(t);
        for(int i = h[t];~i;i=ne[i])
        {
            int j = e[i];
            din[j] --;
            if(din[j] == 0) q.push(j);
        }
    }
    if(res < n) return false;
    return true;
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    memset(h, -1, sizeof h);
    cin >> n >> m; 
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    while (m -- )
    {
        int a,b;
        cin >> a >> b;
        add(a,b);
        din[b]++;
    }
    if(!topo()) cout << "-1" << "\n";
    else
    {
        for(int i = n-1; i >= 0; i --)
        {
            int ver = path[i];
            for(int j = h[ver];~j;j=ne[j])
            {
                int k = e[j];
                for(int s = 0; s < 26; s ++)
                {
                    f[ver][s] = max(f[ver][s],f[k][s]);
                }
            }
            f[ver][a[ver]-'a'] ++;
        }
        int ans = 0;
        for (int i = 1; i <= n; i ++ )
        {
            for(int j = 0; j < 26; j ++)
            {
                ans = max(ans,f[i][j]);
            }
        }
        cout << ans << "\n";
    }
    return 0;
}

3、11-最大化最短路
题意概括:给定一个无向图,还有一群特殊点,选择其中两个点相连,使得1-n的最短路最大。
解题思路: bfs,贪心
(1)假设选择了特殊点中的两个点i,j,那么最短距离为dist1[i]+dist2[j]+1的最大值与不连点之间的最小值。
(2)对于i,j来说,当dist1[i]+dist2[j] <= dist1[j]+dist2[i]->dist1[i]-dist1[j] <= dist2[i]-dist2[j],按照这个规则进行排序,然后枚举每个特殊点

#include 
#include 
#include 
#include 
using namespace std;
const int N = 2e5+10, M = 4e5+10;
int h[N],e[M],ne[M],idx;
int n,m,k;
int s[N];
int dist1[N],dist2[N];
void add(int a,int b)
{
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}
void bfs(int u,int dist[])
{
    memset(dist,0x3f,N*4);
    queue<int> q;
    q.push(u);
    dist[u] = 0;
    while(q.size())
    {
        int t = q.front();
        q.pop();
        for(int i = h[t];~i;i=ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t]+1)
            {
                dist[j] = dist[t]+1;
                q.push(j);
            }
        }
    }
}
bool cmp(int a,int b)
{
    return dist1[a]-dist2[a] < dist1[b] - dist2[b];
}
int main()
{
    memset(h, -1, sizeof h);
    cin >>n >> m >> k;
    for (int i = 1; i <= k; i ++ ) cin >> s[i];
    while (m -- )
    {
        int a,b;
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    bfs(1,dist1);
    bfs(n,dist2);
    sort(s+1,s+1+k,cmp);
    
    int maxx = dist1[s[1]],res = 0;
    for(int i = 2;i <= k; i ++)
    {
        int j = s[i];
        res = max(res,maxx+dist2[j]+1);
        maxx = max(maxx,dist1[j]);
    }
    res = min(res,dist1[n]);
    cout << res << "\n";
    return 0;
}

4、8-选取石子
解题思路:思维
(1)因为a[i] > 0,所以对于一个集合里的元素来讲,越多越好
(2)对于能够放入一个集合的元素要满足 ax-ay = x-y,移项之后可得ax-x = ay-y,于是可以用哈希表将值与坐标差值相同的项求和,同时与数组中最大的元素取最大值。

#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;
const int N = 2e5+10;
int n;
int a[N];
unordered_map<int,LL> mp;
int main()
{
    cin >> n;
    LL ans = 0;
    for (int i = 1; i <= n; i ++ )
    {
        LL x;
        cin >> x;
        ans = max(ans,x);
        mp[x-i] += x;
    }
    for(auto &[x,y] : mp)
    {
        ans = max(ans,y);
    }
    cout << ans << "\n";
    return 0;
}

5、8-更新路线
解题思路:
对于给定的路线,假设路线为 a->b,dist表示当前点到终点的最短距离。分两种情况,如果dist[a] < dist[b]+1,那么就必须更新路线。否则 如果从a到终点的路线不止一条,那么此时可以更新也可以不更新。maxc++
(1)反向建边,求得终点到各个点的最短距离,同时记录下每个点到终点有多少条最短路
(2)遍历待走的点,如果dist[a] < dist[b]+1,那么必须更新。否则判断cnt[a] > 1,那么maxc++

#include 
#include 
#include 
#include 
using namespace std;
const int N = 2e5+10, M = N;
int h[N], e[M], ne[M], idx;
int n,m,k;
int path[N],dist[N],cnt[N];
void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void bfs(int u)
{
    memset(dist,0x3f,sizeof dist);
    dist[u] = 0;
    queue<int> q;
    q.push(u);
    while(q.size())
    {
        int t = q.front();
        q.pop();
        for(int i = h[t];~i;i = ne[i])
        {
            int j = e[i];
            if(dist[j] > dist[t] + 1)
            {
                dist[j] = dist[t] + 1;
                cnt[j] = 1;
                q.push(j);
            }
            else if(dist[j] == dist[t] + 1)
                cnt[j]++;
        }
    }
}
int main()
{
    memset(h, -1, sizeof h);
    cin >> n >> m;
    while (m -- )
    {
        int a,b;
        cin >> a >> b;
        add(b, a); // 反向建边
    }
    cin >> k;
    for (int i = 1; i <= k; i ++ ) cin >> path[i];
    bfs(path[k]);
    int maxc = 0, minc = 0;
    for(int i = 1; i < k; i ++)
    {
        int a = path[i],b = path[i+1];
        if(dist[a] < dist[b] + 1) minc++,maxc++;
        else if(cnt[a] > 1) maxc++;
    }
    cout << minc <<" " << maxc << "\n";
    return 0;
}

6、7-最大剩余油量
解题思路:树形dp
枚举每个以节点i为子树的最大权值和次大权值,走这两条路+当前节点的油量。最后取最大值就行

#include 
#include 
#include 
using namespace std;
typedef long long LL;
const int N = 3e5+10, M = N*2;
int n;
int v[N];
LL ans = 0;
int h[N], e[M], ne[M], w[M],idx;
void add(int a, int b, int c)  // 添加一条边a->b,边权为c
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
LL dfs(int u,int fa)
{
    LL d1 = 0,d2 = 0;
    for(int i = h[u];~i;i = ne[i])
    {
        int j = e[i];
        if(j == fa) continue;
        LL d = dfs(j,u);
        if(d < w[i]) continue;
        d -= w[i];
        if(d >= d1) d2 = d1,d1 = d;
        else if(d > d2) d2 = d;
    }
    ans = max(ans,d1+d2+v[u]);
    return d1+v[u];
}
int main()
{
    memset(h, -1, sizeof h);
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> v[i];
    for(int i = 1; i < n; i ++)
    {
        int a,b,c;
        cin >> a >> b >> c;
        add(a, b, c);
        add(b, a, c);
    }
    dfs(1,-1);
    cout << ans << "\n";
    return 0;
}

7、4-构造有向无环图
题意概括:给定一部分有向边和无向边,要求给定无向边方向,使得整个图是一个DAG图
解题思路:
(1)在建边的时候先不管无向边,只连接有向边
(2)判断这个图的拓扑排序行不行
(3)然后根据拓扑排序的顺序,无向边的方向就是从前一个连向后一个

#include 
#include 
#include 
#include 
using namespace std;
typedef pair<int, int> PII;
const int N = 2e5+10, M = N;
int T,n,m;
vector<int> path;
PII alls[N];
int din[N];
int pos[N];
int h[N], e[M], ne[M], idx;
void add(int a, int b)  // 添加一条边a->b
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void init()
{
    for(int i = 1; i <= n; i ++)
    {
        pos[i] = 0;
        h[i] = -1;
        din[i] = 0;
    }
    idx = 0;
    path.clear();
}
bool topo()
{
    queue<int> q;
    for(int i = 1; i <= n; i ++)
    {
        if(din[i] == 0) q.push(i);
    }
    int res = 0;
    while(q.size())
    {
        int t = q.front();
        q.pop();
        res++;
        path.push_back(t);
        for(int i = h[t];~i;i=ne[i])
        {
            int j = e[i];
            din[j] --;
            if(din[j] == 0) q.push(j);
        }
    }
    if(res < n) return false;
    return true;
}
void Print()
{
    for(int i = 0; i < n; i ++) pos[path[i]] = i;
    for (int i = 1; i <= m; i ++ )
    {
        int a = alls[i].first,b = alls[i].second;
        if(pos[a] < pos[b]) cout << a <<" " << b << "\n";
        else cout << b << " " << a << "\n";
    }
}
int main()
{
    memset(h, -1, sizeof h);
    cin >> T;
    while(T --)
    {
        cin >> n >> m;
        for(int i = 1; i <= m; i ++)
        {
            int type,a,b;
            cin >> type >> a >> b;
            if(type == 1) add(a,b),din[b]++;
            alls[i] = {a,b};
        }
        if(topo())
        {
            cout << "YES" << "\n";
            Print();
        }
        else cout << "NO" << "\n";
        init();
    }
    return 0;
}

3、最大上升子序列和
解题思路:
(1)f[i]表示以i结尾的最长上升子序列和的最大值。f[i] = max(f[0-i-1])+a[i]。
(2)对于求解0~i-1中 < a[i]的所有元素的最大值可以用树状数组来进行动态维护
(3)先进行离散化,对于个a[i],更新树状数组和最大值
动态的维护满足小于a[i]的前缀最大值可以用树状数组来求解

#include 
#include 
#include 
#include 
using namespace std;
const int N = 1e5+10;
typedef long long LL;
LL f[N];
int n;
int a[N];
vector<int> alls;
LL tr[N];
int get(int x)
{
    return lower_bound(alls.begin(),alls.end(),x)-alls.begin()+1;
}
int lowbit(int x)
{
    return x&(-x);
}
void add(int x,LL v)
{
    for(int i = x; i <= n; i += lowbit(i))
        tr[i] = max(tr[i],v);
}
LL query(int x)
{
    LL res = 0;
    for(int i = x; i; i -= lowbit(i)) res = max(res,tr[i]);
    return res;
}
int main()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ )
    {
        int x;
        cin >> x;
        a[i] = x;
        alls.push_back(x);
    }
    sort(alls.begin(),alls.end());
    alls.erase(unique(alls.begin(),alls.end()),alls.end());
    
    LL ans = 0;
    for (int i = 1; i <= n; i ++ )
    {
        int idx = get(a[i]);
        f[i] = query(idx-1)+a[i];
        ans = max(ans,f[i]);
        add(idx,f[i]);
    }
    cout << ans << "\n";
    return 0;
}

你可能感兴趣的:(做题总结,c++,算法)