2018 Multi-University Training Contest 3

Problem A. Ascending Rating

先学习一下单调队列与单调栈,其实和STL的也没啥区别,就是手动维护数组。

POJ 2823

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e6 + 50;
struct node
{
    int x, val;
};
node q[maxn * 2];
int minv[maxn], maxv[maxn];
int a[maxn];
int n, k;
int main()
{
    scanf("%d %d", &n, &k);
    for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
    int head = 1, tail = 0;
    int i = 1;
    q[++tail].val = a[i], q[tail].x = i;
    i++;
    for(; i < k; i++)
    {
        while(head <= tail && q[tail].val < a[i])
        {
            tail--;
        }
        q[++tail].val = a[i], q[tail].x = i;
    }
    if(k == 1) maxv[1] = q[head].val;
    for(; i <= n; i++)
    {
        while(head <= tail && q[tail].val < a[i])
        {
            tail--;
        }
        q[++tail].val = a[i], q[tail].x = i;
        if(q[head].x + k <= i) head++;
        maxv[i - k + 1] = q[head].val;
        //printf("%d
", maxv[i - k + 1]);
    }
    head = 1, tail = 0;
    q[++tail].val = a[1], q[tail].x = 1;
    i = 2;
    for(; i < k; i++)
    {
        while(head <= tail && q[tail].val > a[i])
        {
            tail--;
        }
        q[++tail].val = a[i], q[tail].x = i;
    }
    if(k == 1) minv[1] = q[head].val;
    for(; i <= n; i++)
    {
        while(head <= tail && q[tail].val > a[i])
        {
            tail--;
        }
        q[++tail].val = a[i], q[tail].x = i;
        if(q[head].x + k <= i) head++;
        minv[i - k + 1] = q[head].val;
    }
    int t = n - k + 1;
    for(int i = 1; i <= t; i++)
    {
        printf("%d", minv[i]);
        if(i < t) printf(" ");
        else printf("
");
    }
    for(int i = 1; i <= t; i++)
    {
        printf("%d", maxv[i]);
        if(i < t) printf(" ");
        else printf("
");
    }
    return 0;
}
/*
8 1
1 3 -1 -3 5 3 6 7
*/
Code

HDU 3530

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 50;
int q1[maxn * 2], q2[maxn * 2], num[maxn];
int main()
{
    int n, m, k;
    while(scanf("%d %d %d", &n, &m, &k) != EOF)
    {
        for(int i = 1; i <= n; i++) scanf("%d", &num[i]);
        int he1 = 1, ta1 = 0, he2 = 1, ta2 = 0, ans = 0, cur = 1;
        for(int i = 1; i <= n; i++)
        {
            while(he1 <= ta1 && num[q1[ta1]] < num[i]) ta1--;
            while(he2 <= ta2 && num[q2[ta2]] > num[i]) ta2--;
            q1[++ta1] = i, q2[++ta2] = i;
            while(he1 <= ta1 && he2 <= ta2 && num[q1[he1]] - num[q2[he2]] > k)
            {
                if(q1[he1] < q2[he2]) cur = q1[he1++] + 1;
                else cur = q2[he2++] + 1;
            }
            if(he1 <= ta1 && he2 <= ta2 && num[q1[he1]] - num[q2[he2]] >= m)
            {
                ans = max(ans, i - cur + 1);
            }
        }
        printf("%d
", ans);
    }
    return 0;
}
Code

 本题和POJ2823很像,只不过是倒着扫,维护一个单调递减的区间。队列内的元素个数就是某个区间的最大值的变化数。

为什么不能正着扫呢? 维护单调递增区间,不好处理那些在最大值后面的小值,把最大值去了,那些也没法加了。 可能有更麻烦的做法。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e7 + 50;
typedef long long ll;
int n, m, k, p, q, r;
ll MOD;
int a[maxn];
int qq[maxn];
int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%d %d %d %d %d %d %lld", &n, &m, &k, &p, &q, &r, &MOD);
        for(int i = 1; i <= n; i++)
        {
            if(i <= k) scanf("%d", &a[i]);
            else a[i] = ((ll)p * a[i - 1] + (ll)q * i  + (ll)r) % MOD;
        }
        int head = 1, tail = 0;
        ll A = 0, B = 0;
        int i = n;
        for(; i > n - m + 1; i--)
        {
            while(head <= tail && a[qq[tail]] <= a[i]) tail--;
            qq[++tail] = i;
        }
        for(; i >= 1; i--)
        {
            while(head <= tail && a[qq[tail]] <= a[i]) tail--;
            qq[++tail] = i;
            if(head <= tail && qq[head] >= i + m) head++;
            A += (ll)a[qq[head]] ^ i;
            B += (ll)(tail - head + 1) ^ i;
           // printf("%d %d
", a[qq[head]], (tail - head + 1));
        }
        printf("%lld %lld
", A, B);
    }
    return 0;
}





/*
1
13 6 13 5 5 5 5
13 12 11 10 9 8 7 6 5 4 3 2 1
1
10 1 10 5 5 5 5
3 2 2 1 5 7 6 8 2 9
*/
Code

Problem C. Dynamic Graph Matching

$f[i][s]$表示经过前i次操作后,s集合的点已经匹配的方案数。

加边操作,递推公式:$f[i][s]=f[i-1][s]+f[i-1][s-u-v]。$

$f[i-1][s]$表示不使用这条边的情况,$f[i-1][s-u-v]$表示使用这条边的情况,相加就得到添加一条边的情况。

删边操作$f[i][s]=f[i-1][s]-f[i-1][s-u-v]$ 就可以了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1111;
int f[maxn];
const ll mod = 1e9 + 7;
int ans[11];
int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        memset(f, 0, sizeof(f));
        int n, m;
        scanf("%d %d", &n, &m);
        char fu[5];
        int u, v;
        f[0] = 1;
        for(int i = 1; i <= m; i++)
        {
            scanf("%s %d %d",fu, &u, &v);
            u--, v--; ///从0开始表示点
            memset(ans, 0, sizeof(ans));
            for(int s = 0; s < (1 << n); s++)
            {
                if(((1 << u) & s) && ((1 << v) & s))
                {
                    int ccc = 0;
                    if(fu[0] == '+') f[s] = ((ll)f[s] + f[s - (1 << u) - (1 << v)]) % mod;
                    else f[s] = ((ll)f[s] - f[s - (1 << u) - (1 << v)] + mod) % mod;
                }
                if(f[s] != 0)
                {
                    int st = s;
                    int cnt = 0;
                    while(st)
                    {
                        if(st & 1) cnt++;
                        st >>= 1;
                    }
                    ans[cnt / 2] = ((ll)ans[cnt / 2] + f[s]) % mod;
                }
            }
            for(int ge = 1; ge <= (n >> 1); ge++)
            {
                printf("%d", ans[ge]);
                if(ge < (n >> 1)) printf(" ");
                else printf("
");
            }
        }
    }
    return 0;
}
Code

Problem G. Interstellar Travel

先学习一发极角排序。

POJ 2007

逆时针走起,符合逆时针则 $>0。$

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100;
struct Point
{
    Point(int _x, int _y)
    {
        x = _x;
        y = _y;
    }
    Point(){}
    int x, y;
};
Point point[maxn];
Point operator - (Point A, Point B)
{
    return Point(A.x - B.x, A.y - B.y);
}
int Cross(Point A, Point B)
{
    return (A.x * B.y - A.y * B.x);
}
bool cmp(Point A, Point B)
{
    int tmp = Cross(A - point[0], B - point[0]);
    if(tmp > 0) return 1;
    else if(tmp < 0) return false;
}

int main()
{
    int cnt = 0;
    while(~scanf("%d %d", &point[cnt].x, &point[cnt].y))
    {
        cnt++;
    }
    sort(point + 1, point + cnt, cmp);
    for(int i = 0; i < cnt; i++)
    {
        printf("(%d,%d)
", point[i].x, point[i].y);
    }
    return 0;
}
Code

 整理个自己的凸包模板。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e3 + 50;
const double PI = acos(-1.0);
struct Point
{
    Point(int _x, int _y)
    {
        x = _x;
        y = _y;
    }
    Point(){}
    int x, y;

};
Point point[maxn];
Point operator - (Point A, Point B)
{
    return Point(A.x - B.x, A.y - B.y);
}
int Cross(Point A, Point B)
{
    return (A.x * B.y - A.y * B.x);
}
Point pmin;
double dis(Point A, Point B)
{
    return sqrt((double)(A.x - B.x) * (A.x - B.x) + (double)(A.y - B.y) * (A.y - B.y));
}
bool cmp(Point A, Point B)
{
    int tmp = Cross(A - point[1], B - point[1]);
    if(tmp > 0) return 1;
    else if(tmp == 0 && dis(A , pmin) < dis(B , pmin)) return 1;
    else  return false;
}
int top = 0;
int st[maxn];
void graham(int n)
{
    if(n == 1)
    {
        top = 1, st[1] = 1;
    }
    else if(n == 2)
    {
        top = 2, st[1] = 1, st[2] = 2;
    }
    else
    {
        for(int i = 1; i <= 2; i++) st[i] = i;
        top = 2;
        for(int i = 3; i <= n; i++)
        {
            while(top >= 2 && Cross(point[st[top]] - point[st[top - 1]], point[i] - point[st[top - 1]]) <= 0)
            {
                top--;
            }
            top++;
            st[top] = i;
        }
    }
}
double f(int x, int y)
{
    return (double)x * x + (double)y * y;
}
int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        int n, L;
        scanf("%d %d", &n, &L);
        for(int i = 1; i <= n; i++) scanf("%d %d", &point[i].x, &point[i].y);
        ///要先找到最下,最左的点作为原点
        int k = 1;
        pmin.x = point[1].x, pmin.y = point[1].y;
        for(int i = 2; i <= n; i++)
        {
            if(pmin.y > point[i].y || (pmin.y == point[i].y && pmin.x > point[i].x))
            {
                pmin.y = point[i].y;
                pmin.x = point[i].x;
                k = i;
            }
        }
        point[k] = point[1];
        point[1] = pmin;
        sort(point + 2, point + n + 1, cmp);
        top = 0;
        graham(n);
        double C = 0;
        for(int i = 1; i < top; i++)
        {
            C += (double)sqrt(f(point[st[i]].x - point[st[i+1]].x, point[st[i]].y - point[st[i+1]].y));
         //   printf("%lf
", C);
        }
        C += (double)sqrt(f(point[st[top]].x - point[st[1]].x, point[st[top]].y - point[st[1]].y));
        long long ans = 0;
        C += (double)2.0 * PI * L;
        printf("%.0lf
",C);
        if(T!=0) printf("
");
    }
    return 0;
}
Code

 去重点,去共线。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 50;
const double PI = acos(-1.0);
struct Point
{
    Point(ll _x, ll _y)
    {
        x = _x;
        y = _y;
    }
    Point(){}
    ll x, y;
    int id;

};
Point point[maxn];
Point operator - (Point A, Point B)
{
    return Point(A.x - B.x, A.y - B.y);
}
ll Cross(Point A, Point B)
{
    return (A.x * B.y - A.y * B.x);
}
ll dis(Point A, Point B)
{
    return (A.x - B.x) * (A.x - B.x) + (A.y - B.y) * (A.y - B.y);
}
bool cmp(Point A, Point B)
{
    ll tmp = Cross(A - point[1], B - point[1]);
    if(tmp < 0LL) return 1;
    else if(tmp == 0 && dis(A , point[1]) < dis(B , point[1])) return 1;
    else if(tmp == 0 && dis(A , point[1]) == dis(B , point[1]))
    {
        return A.id < B.id;
    }
    else  return 0;
}
int top = 0;
int st[maxn];
void graham(int n)
{
    if(n == 1)
    {
        top = 1, st[1] = 1;
    }
    else if(n == 2)
    {
        top = 2, st[1] = 1, st[2] = 2;
    }
    else
    {
        for(int i = 1; i <= 2; i++) st[i] = i;
        top = 2;
        for(int i = 3; i <= n; i++)
        {
            if(point[i].x == point[st[top]].x && point[i].y == point[st[top]].y) continue;
            while(top >= 2 && Cross(point[st[top]] - point[st[top - 1]], point[i] - point[st[top - 1]]) >= 0)
            {
                if(Cross(point[st[top]] - point[st[top - 1]], point[i] - point[st[top - 1]]) > 0)
                {
                    top--;
                }
                else
                {
                    if(point[st[top]].id > point[i].id) ///共线,top的点属于线段中间的点,字典序较大,不符
                    {
                        top--;
                    }
                    else break;
                }
            }
            top++;
            st[top] = i;
        }
    }
}
int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        int n;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%lld %lld", &point[i].x, &point[i].y), point[i].id = i;
        ///要先找到最下,最左的点作为原点
        sort(point + 2, point + n + 1, cmp);
        top = 1;
        graham(n);
        for(int i = 1; i <= top; i++)
        {
            printf("%d", point[st[i]].id);
            if(i < top) printf(" ");
            else printf("
");
        }
    }
    return 0;
}
/*
123
7
0 0
3 2
1 1
2 3
3 2
4 0
1 3
*/
Code

Aguin的写法真的是天秀。。。。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 10;
ll x[maxn], y[maxn], id[maxn], st[maxn];
bool cmp(int i, int j)
{
    if(x[i] != x[j]) return x[i] < x[j];
    if(y[i] != y[j]) return y[i] > y[j];
    return i < j;
}
ll cross(int i, int j, int k)
{
    return ((x[i] - x[k]) * (y[j] - y[k]) - (x[j] - x[k]) * (y[i] - y[k]));
}
vector<ll> can;
int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        int n; scanf("%d", &n);
        for(int i = 1; i <= n; i++)
        {
            scanf("%lld %lld", &x[i], &y[i]);
            id[i] = i;
        }
        sort(id + 1, id + n + 1, cmp);
        can.clear();
        for(int i = 1; i <= n; i++) ///去掉重点,和x相等,较低的点
        {
            if(i == 1 || x[id[i]] > x[id[i - 1]]) can.push_back(id[i]);
        }
        int top = 0;
        for(int i = 0; i < can.size(); i++)
        {
            while(top >= 2 && cross(st[top], can[i], st[top - 1]) > 0) top--;
            while(top >= 2 && cross(st[top], can[i], st[top - 1]) == 0 && can[i] < st[top]) top--;
            st[++top] = can[i];
        }
        for(int i = 1; i <= top; i++) printf("%lld%c", st[i], i == top ? '
' : ' ');
    }
    return 0;
}

/*
123
7
0 0
3 2
1 1
2 3
3 2
4 0
1 3
*/
Code

Problem I. Random Sequence

不计复杂度的写法是:

$dp[i][a[i]][a[i-1]][a[i-2]]+=dp[i-1][a[i-1]][a[i-2]][a[i-3]] imes v[gcd(a[i],a[i-1],a[i-2],a[i-3])]$

复杂度应该是$O(m^4n)。$

然后我们发现,在求$gcd(a[i],a[i-1],a[i-2],a[i-3])$的时候,如果$a[i]=2$,那么$a[i-1]=4$或者$6$都已经不重要,因为此时它的$gcd$最大是$2。$

所以我们可以令

$v1=a[i-1];$

$v2=gcd(a[i-1],a[i-2]);$

$v3=gcd(a[i-1],a[i-2],a[i-3]);$

所以$v2$是$v1$的因子,即$v1|v2,v2|v3.$

带入转移方程中:

$dp[i][a[i]][gcd(a[i],v1)][gcd(a[i],v2)]+=dp[i-1][v1][v2][v3] imes v[gcd(a[i], v3)].$

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int cnt = 0;
    for(int i = 1; i <= 100; i++)
    {
        for(int j = 1; j <= 100; j++)
        {
            for(int k = 1; k <= 100; k++)
            {
                if(i % j == 0 && j % k == 0)
                {
                    cnt++;
                    printf("%d %d %d
", i, j, k);
                }
            }
        }
    }
    printf("cnt = %d
", cnt);
}
……
100 100 5
100 100 10
100 100 20
100 100 25
100 100 50
100 100 100
cnt = 1471

打表出来的状态只有$1471$个。

复杂度$O(nmS).$

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 1e9 + 7;
ll quick_pow(ll a, ll n)
{
    ll res = 1;
    while(n)
    {
        if(n & 1) res = a * res % mod;
        n >>= 1;
        a = a * a % mod;
    }
    return res;
}
int gcd[101][101];
int id[101][101][101], a[101];
struct node
{
    int v1, v2, v3;
}st[1500];
int cnt = 0;
ll inv[101];
void init()
{
    for(int i = 1; i <= 100; i++) inv[i] = quick_pow(i, mod - 2);
    for(int i = 1; i <= 100; i++)
    {
        for(int j = 1; j <= 100; j++)
        {
            gcd[i][j] = __gcd(i, j);
        }
    }
    /// a[i-1]  gcd(a[i-1],a[i-2]) gcd(a[i-1],a[i-2],a[i-3])
    for(int i = 1; i <= 100; i++)
    {
        for(int j = 1; j <= i; j++)
        {
            if(i % j != 0) continue;
            for(int k = 1; k <= j; k++)
            {
                if(j % k != 0)continue;
                st[++cnt] = node{i, j, k};
                id[i][j][k] = cnt;
            }
        }
    }
}
ll v[101], dp[101][1500];
int main()
{
    init();
    ///printf("%d
", cnt);  1471
    int T; scanf("%d", &T);
    while(T--)
    {
        memset(dp, 0, sizeof(dp));
        int n, m; scanf("%d %d", &n, &m);
        for(int i = 1; i <= n; i++) scanf("%d", a + i);
        for(int i = 1; i <= m; i++) scanf("%lld", v + i);
        ///初始化前三个
        for(int i = 1; i <= m; i++)
        {
            if(a[3] && a[3] != i) continue;
            for(int j = 1; j <= m; j++)
            {
                if(a[2] && a[2] != j) continue;
                for(int k = 1; k <= m; k++)
                {
                    if(a[1] && a[1] != k) continue;
                    int v1 = i;
                    int v2 = gcd[v1][j];
                    int v3 = gcd[v2][k];
                    int net = id[v1][v2][v3];
                    dp[3][net]++;
                   // printf("%d %d %d
", v1, v2, v3);
                }
            }
        }
        for(int i = 3; i < n; i++) ///枚举的是a[i+1]
        {
            for(int j = 1; j <= cnt; j++)
            {
                if(dp[i][j])
                {
                    int v1 = st[j].v1, v2 = st[j].v2, v3 = st[j].v3;
                    for(int k = 1; k <= m; k++)
                    {
                        if(a[i + 1] && a[i + 1] != k) continue;
                        int net = id[k][gcd[k][v1]][gcd[k][v2]];
                       // printf("%d %d
", i + 1, net);
                        dp[i + 1][net] = (dp[i + 1][net] + dp[i][j] * v[gcd[k][v3]] % mod) % mod;
                       // printf("%d %lld %lld
", gcd[k][v3], v[gcd[k][v3]], dp[i+1][net]);
                    }
                }
            }
        }
        ll ans = 0;
        for(int i = 1; i <= cnt; i++)
        {
            ans = (ans + dp[n][i]) % mod;
        }
        for(int i = 1; i <= n; i++)
        {
            if(!a[i]) ans = (ans * inv[m]) % mod;
        }
        printf("%lld
", ans);
    }
    return 0;
}
/*
23
4 3
0 0 0 0
3 2 4
*/
Code

Problem M. Walking Plan

首先最容易想到的状态是$G[i][j][k]$,表示从$i$到$j$最少经过$k$条路径。

状态转移方程是:$G[i][j][k]=min(G[i][j][k],G[i][p][k-1]+g[p][j])$,表示从$p$到$j$有一条路径。

时间复杂度是$O(n^3 k).$  肯定超时。

然后就真香系列……

https://blog.csdn.net/qq_34454069/article/details/81292814

注意此处DPa应该是DPb。

还有个坑点是$DPc[i][j][k]$枚举至少经过$k$条路径的时候,应该从$k=0$开始。因为询问可以是$100$的整数倍。

#include <bits/stdc++.h>
using namespace std;
int w[55][55];
int dist[55][55]; ///跑一遍Floyd求两点最短路
const int INF = 0x3f3f3f3f;
///k <= 100
int dp0[51][51][101]; ///经过刚好k*100条边
int dp1[51][51][101]; ///刚好经过k条边
int dp2[51][51][101]; ///至少经过k条边的最短路
int main()
{
   // printf("%d
", INF + INF);
    int T; scanf("%d", &T);
    while(T--)
    {
        int n, m; scanf("%d %d", &n, &m);
        memset(w, INF, sizeof(w));
        memset(dist, INF, sizeof(dist));
        memset(dp0, INF, sizeof(dp0));
        memset(dp1, INF, sizeof(dp1));
        memset(dp2, INF, sizeof(dp2));
        for(int i = 1; i <= m; i++)
        {
            int u, v, val; scanf("%d %d %d", &u, &v, &val);
            w[u][v] = min(w[u][v], val);
            dist[u][v] = w[u][v];
        }
        for(int i = 1; i <= n; i++)
        {
            dist[i][i] = 0;
            dp1[i][i][0] = 0;
            dp0[i][i][0] = 0;
            dp2[i][i][0] = 0;
        }
        ///Floyd
        for(int k = 1; k <= n; k++)
        {
            for(int i = 1; i <= n; i++)
            {
                for(int j = 1; j <= n; j++)
                {
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
                }
            }
        }
        ///刚好经过k条边
        for(int k = 1; k <= 100; k++)
        {
            for(int l = 1; l <= n; l++)
            {
                for(int i = 1; i <= n; i++)
                {
                    for(int j = 1; j <= n; j++)
                    {
                        dp1[i][j][k] = min(dp1[i][j][k], dp1[i][l][k - 1] + w[l][j]);
                    }
                }
            }
        }
       // printf("%d
", dist[2][2]);
        ///刚好经过k*100条边
        for(int k = 1; k <= 100; k++)
        {
            for(int l = 1; l <= n; l++)
            {
                for(int i = 1; i <= n; i++)
                {
                    for(int j = 1; j <= n; j++)
                    {
                        dp0[i][j][k] = min(dp0[i][j][k], dp0[i][l][k - 1] + dp1[l][j][100]);
                    }
                }
            }
        }
        ///至少经过k条边
        for(int k = 0; k <= 100; k++)
        {
            for(int l = 1; l <= n; l++)
            {
                for(int i = 1; i <= n; i++)
                {
                    for(int j = 1; j <= n; j++)
                    {
                        dp2[i][j][k] = min(dp2[i][j][k], dp1[i][l][k] + dist[l][j]);
                    }
                }
            }
        }
        int q; scanf("%d", &q);
        while(q--)
        {
            int s, t, k;
            scanf("%d %d %d", &s, &t, &k);
            int ans = INF;
            for(int i = 1; i <= n; i++)
            {
                ans = min(ans, dp0[s][i][k / 100] + dp2[i][t][k % 100]);
            }
            printf("%d
", ans == INF ? -1 : ans);
        }
    }
    return 0;
}
Code
原文地址:https://www.cnblogs.com/littlepear/p/9394064.html