大二下第一周学习笔记

因为疫情推迟返校,在家上网课。

在家的话,自律非常重要,开启自律模式

周四

F. Subsequences of Length Two(dp)

一开始想的是贪心,然后发现不太对。

突然醒悟到dp,显然状态和前i个,用了多少此有关

当前是t[1]的话,还和前面有多少个t[0]有关

于是把有多少个t[0]也加入状态

最后就是一个n^3的dp

一开始给200的数据量,就提示时间复杂度了,做了那么多题,一般没有不拉满的

注意特判一下t的两个字母一样的情况

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 200 + 10;
int dp[N][N][N], n, k, ans;
string s, T;

int main()
{
    cin >> n >> k >> s >> T;
    s = " " + s;

    if(T[0] == T[1])
    {
        int cnt = 0, len = s.size();
        _for(i, 1, len) cnt += (s[i] == T[0]);
        cnt = min(cnt + k, n);
        printf("%d\n", cnt * (cnt - 1) / 2);
        return 0;
    }

    memset(dp, -0x3f, sizeof dp);
    dp[0][0][0] = 0;
    _for(i, 1, n)
        _for(j, 0, k)
            _for(t, 0, i)
            {
                if(s[i] == T[0])
                {
                    if(t > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j][t - 1]);
                    if(j > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j - 1][t] + t);
                }
                else if(s[i] == T[1])
                {
                    dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j][t] + t);
                    if(j > 0 && t > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j - 1][t - 1]);
                }
                else
                {
                    dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j][t]);
                    if(j > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j - 1][t] + t);
                    if(j > 0 && t > 0) dp[i][j][t] = max(dp[i][j][t], dp[i - 1][j - 1][t - 1]);
                }
                ans = max(ans, dp[i][j][t]);
            }
    printf("%d\n", ans);

    return 0;
}

周五

keep going 不要停止提升自己

B. Present(异或)

这道题自己想没什么思路,只知道每一位拆开来考虑,然后看最后这一位的奇偶,只能想到这

其实比较靠近了,正解是直接考虑答案的某一位是否为1

比如当前看第k位,那么显然要考虑哪些数对加起来第k位有1,要统计这个个数。

如果两个数加起来第k位有1,那么起码是大于2^k的 但是上限很大,而且大于也不一定这一位为1

那么我们加一个限制条件,每一个数模2 ^ (k + 1) 这样子第一不会影响答案,更高位不影响低位,第二限制了上限

这样处理了之后,两个数加起来第k位有1的话,可以写出取值的区间。

所以枚举每一个数,看谁和它加起来在这个区间里面,这个可以用二分来求,因为要二分所以要先排序。最后要注意去掉自己和自己,以及重复统计。

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 4e5 + 10;
int a[N], b[N], n, ans;

int main()
{
    scanf("%d", &n);
    _for(i, 1, n) scanf("%d", &a[i]);

    _for(k, 0, 25)
    {
        _for(i, 1, n) b[i] = a[i] % (1 << (k + 1));
        int cnt = 0;
        sort(b + 1, b + n + 1);
        _for(i, 1, n)
        {
            int l = lower_bound(b + 1, b + n + 1, (1 << k) - b[i]) - b;
            int r = upper_bound(b + 1, b + n + 1, (1 << (k + 1)) - 1 - b[i]) - b - 1;
            cnt += r - l + 1;
            l = lower_bound(b + 1, b + n + 1, (1 << (k + 1)) + (1 << k) - b[i]) - b;
            r = upper_bound(b + 1, b + n + 1, (1 << (k + 2)) - 2 - b[i]) - b - 1;
            cnt += r - l + 1;
            if((b[i] + b[i]) & (1 << k)) cnt--;
        }
        if((cnt / 2) & 1) ans += 1 << k;
    }
    printf("%d\n", ans);

    return 0;
}

D. Segment Tree(set+暴力优化)

核心思想是暴力建边,但边数小于等于n - 1的,所以可以保证复杂度。
那么怎么优化呢,先排序,对于当前区间,要看前面区间有多少个右端点在当前区间的

我自己想的时候想到了暴力遍历,树状数组统计个数,但应该用set存右端点二分来找。

提前预处理一下每个右端点对应的标号。用并查集维护一下成不成环。

#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e5 + 10;
pair<int, int> a[N];
unordered_map<int, int> mp;
int f[N], n, cnt;
set<int> s;

int find(int x) { return x == f[x] ? x : f[x] = find(f[x]); }
void Union(int x, int y) { f[find(x)] = find(y); }

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int l, r;
        scanf("%d%d", &l, &r);
        a[i] = {l, r};
    }
    sort(a + 1, a + n + 1);
    _for(i, 1, n) mp[a[i].second] = i, f[i] = i;

    s.insert(a[1].second);
    _for(i, 2, n)
    {
        auto itl = s.lower_bound(a[i].first);
        auto itr = s.lower_bound(a[i].second);
        for(auto it = itl; it != itr; it++)
        {
            if(++cnt >= n || find(i) == find(mp[*it]))
            {
                puts("NO");
                return 0;
            }
            Union(i, mp[*it]);
        }
        s.insert(a[i].second);
    }
    puts(cnt == n - 1 ? "YES" : "NO");

    return 0;
}

E. Soldier and Traveling(最大流+输出方案)

最大流题的数据范围只有几百,而且这道题很明显一个士兵看作一股流,也就是最大流问题。

分两个部分,建图和输出方案

建图的话,一个点拆成两个点,源点给入点ai的流量,出点给汇点bi的流量。
然后,有连边以及自己对自己就入点向出点连容量为无限的边。

满流了就符合条件,输出的时候遍历每一条边。

记住有正向边和反向边,因为正向边流了多少,反向边就加多少,所以遍历反向边就可以知道流了多少。

#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;

typedef long long ll;
const int N = 210;
struct Edge { int from, to, flow; };
vector<Edge> edge;
vector<int> g[N];
int d[N], cur[N], ans[N][N];
int a[N], b[N], n, m, s, t, suma, sumb;

void add(int from, int to, int flow)
{
	edge.push_back(Edge{from, to, flow});
	g[from].push_back(edge.size() - 1);
	edge.push_back(Edge{to, from, 0});
	g[to].push_back(edge.size() - 1);
}

bool bfs()
{
	memset(d, 0, sizeof(d));
	queue<int> q;
	q.push(s);
	d[s] = 1;

	while(!q.empty())
	{
		int u = q.front(); q.pop();
		for(auto x: g[u])
		{
            Edge e = edge[x];
            if(!d[e.to] && e.flow)
            {
                d[e.to] = d[u] + 1;
                q.push(e.to);
            }
		}
	}

	return d[t];
}

ll dfs(int u, ll in)
{
	if(u == t) return in;
	ll out = 0;
	for(int& i = cur[u]; i < g[u].size(); i++)
		{
			Edge& e = edge[g[u][i]];
			if(d[u] + 1 == d[e.to] && e.flow)
			{
			    ll f = dfs(e.to, min((ll)e.flow, in));
				e.flow -= f;
				edge[g[u][i] ^ 1].flow += f;
				out += f; in -= f;
				if(in == 0) break;
			}
		}
	return out;
}

void build()                                    
{
    scanf("%d%d", &n, &m);
    s = 0; t = 201;
    _for(i, 1, n) scanf("%d", &a[i]), add(s, i, a[i]), add(i, i + n, 1e9), suma += a[i];
    _for(i, 1, n) scanf("%d", &b[i]), add(i + n, t, b[i]), sumb += b[i];
	while(m--)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v + n, 1e9);
		add(v, u + n, 1e9);
	}
}

int main()
{
    build();

	ll maxflow = 0;
	while(bfs())
	{
	    memset(cur, 0, sizeof(cur));
        maxflow += dfs(s, 1e18);
	}

	if(suma != sumb || suma != maxflow)
    {
        puts("NO");
        return 0;
    }

    puts("YES");
    for(auto e: edge)
        if(e.to < e.from && e.from > n)
            ans[e.to][e.from - n] = e.flow;
    _for(i, 1, n)
    {
        _for(j, 1, n)
            printf("%d ", ans[i][j]);
        puts("");
    }

	return 0;
}

周六

B. Destroying Roads(最短路+分类讨论)

这道题受之前一道题的起发,之前那道题是暴力枚举相交点。

这题类似,暴力枚举路径重叠的部分。

注意重叠的时候是有方向性的,也是有两种情况,我开始WA了一发。

#include <bits/stdc++.h>
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
using namespace std;

const int N = 3000 + 10;
int s1, t1, s2, t2, l1, l2, n, m;
vector<int> g[N];
int dis[N][N];

void bfs(int s, int d[])
{
    _for(i, 1, n) d[i] = 1e9;
    d[s] = 0;
    queue<int> q;
    q.push(s);

    while(!q.empty())
    {
        int u = q.front(); q.pop();
        for(int v: g[u])
            if(d[u] + 1 < d[v])
            {
                d[v] = d[u] + 1;
                q.push(v);
            }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, m)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
    scanf("%d%d%d%d%d%d", &s1, &t1, &l1, &s2, &t2, &l2);
    _for(i, 1, n) bfs(i, dis[i]);

    int ans = -1;
    if(dis[s1][t1] <= l1 && dis[s2][t2] <= l2) ans = max(ans, m - dis[s1][t1] - dis[s2][t2]);
    _for(i, 1, n)
        _for(j, 1, n)
            if(dis[s1][i] + dis[i][j] + dis[j][t1] <= l1 && dis[s2][i] + dis[i][j] + dis[j][t2] <= l2)
                ans = max(ans, m - (dis[s1][i] + dis[i][j] + dis[j][t1] + dis[s2][i] + dis[j][t2]));
    swap(s1, t1);
    _for(i, 1, n)
        _for(j, 1, n)
            if(dis[s1][i] + dis[i][j] + dis[j][t1] <= l1 && dis[s2][i] + dis[i][j] + dis[j][t2] <= l2)
                ans = max(ans, m - (dis[s1][i] + dis[i][j] + dis[j][t1] + dis[s2][i] + dis[j][t2]));
    printf("%d\n", ans);

	return 0;
}

Golf Bot(FFT)

FFT就是多项式乘积优化成nlogn

这类题一般是构造多项式相乘

这道题就是给两个序列a,b

求b序列中有多少个数可以表示为a序列中的一个数,或者两个数之和(可以相同)

可以构造两个一样的多项式,a序列有相应的数的项的系数为1,否则为0

因为还有1个数的情况,所以看作a中有一个0。

这样用多项式乘法,就可以得出所有的和。

注意数据

此题代码可用作模板

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 8e5 + 10;  //最高次数4倍
const double pi = acos(-1.0);

struct Complex
{
    double x, y;
}a[N], b[N], c[N];
int ans[N], r[N], n, m, l, limit;

Complex operator + (Complex a, Complex b) { return Complex{a.x + b.x, a.y + b.y}; }
Complex operator - (Complex a, Complex b) { return Complex{a.x - b.x, a.y - b.y}; }
Complex operator * (Complex a, Complex b) { return Complex{a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x}; }

void FFT(Complex *A, int type)
{
    rep(i, 0, limit)
        if(i < r[i])
            swap(A[i], A[r[i]]);

    for(int mid = 1; mid < limit; mid <<= 1)
    {
        Complex Wn{cos(pi / mid), type * sin(pi / mid)};
        for(int R = mid << 1, j = 0; j < limit; j += R)
        {
            Complex w{1, 0};
            for(int k = 0; k < mid; k++, w = w * Wn)
            {
                Complex x = A[j + k], y = w * A[j + mid + k];
                A[j + k] = x + y;
                A[j + mid + k] = x - y;
            }
        }
    }
}

void solve()
{
    for(limit = 1, l = 0; limit <= n + m; limit <<= 1, l++);
    rep(i, 0, limit) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
    FFT(a, 1); FFT(b, 1);
    _for(i, 0, limit) c[i] = a[i] * b[i];
    FFT(c, -1);
    _for(i, 0, n + m) ans[i] = (int)(c[i].x / limit + 0.5);
}

int main()
{
    while(~scanf("%d", &n))
    {
        memset(a, 0, sizeof a);
        memset(b, 0, sizeof b);

        a[0].x = b[0].x = 1;
        _for(i, 1, n)
        {
            int x; scanf("%d", &x);
            a[x].x = b[x].x = 1;
        }
        m = n = 2e5;

        solve();

        int cnt = 0;
        scanf("%d", &m);
        while(m--)
        {
            int x; scanf("%d", &x);
            cnt += (ans[x] > 0);
        }
        printf("%d\n", cnt);
    }

    return 0;
}

FFT模板

整理模板

/*
FFT模板 以此题为例
这道题就是给两个序列a,b

求b序列中有多少个数可以表示为a序列中的一个数,或者两个数之和(可以同一个数)

可以构造两个一样的多项式,a序列有相应的数的项的系数为1,否则为0

因为还有1个数的情况,所以看作a中有一个0。

这样用多项式乘法,就可以得出所有的和。

*/

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 8e5 + 10;  //答案多项式次数的两倍 也是limit的理论最大值
const double pi = acos(-1.0);

struct Complex
{
    double x, y;
}a[N], b[N], c[N];
int ans[N], r[N], n, m, l, limit;

Complex operator + (Complex a, Complex b) { return Complex{a.x + b.x, a.y + b.y}; }
Complex operator - (Complex a, Complex b) { return Complex{a.x - b.x, a.y - b.y}; }
Complex operator * (Complex a, Complex b) { return Complex{a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x}; }

void FFT(Complex *A, int type)
{
    rep(i, 0, limit)
        if(i < r[i])
            swap(A[i], A[r[i]]);

    for(int mid = 1; mid < limit; mid <<= 1)
    {
        Complex Wn{cos(pi / mid), type * sin(pi / mid)};
        for(int R = mid << 1, j = 0; j < limit; j += R)
        {
            Complex w{1, 0};
            for(int k = 0; k < mid; k++, w = w * Wn)
            {
                Complex x = A[j + k], y = w * A[j + mid + k];
                A[j + k] = x + y;
                A[j + mid + k] = x - y;
            }
        }
    }
}

void solve()
{
    for(limit = 1, l = 0; limit <= n + m; limit <<= 1, l++);      //FFT只能处理2^t的多项式 limit表示最高次数 n + m即答案多项式次数
    rep(i, 0, limit) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
    FFT(a, 1); FFT(b, 1);                                         //FFT 系数表示法转化为点值表示法
    _for(i, 0, limit) c[i] = a[i] * b[i];                         //点值表示法直接相乘
    FFT(c, -1);                                                   //逆FFT 点值表示法转化为系数表示法 最后要多除个limit(下一行)
    _for(i, 0, n + m) ans[i] = (int)(c[i].x / limit + 0.5);
}

int main()
{
    while(~scanf("%d", &n))
    {
        memset(a, 0, sizeof a);                                   //多组数据只用清空a b两个多项式的数组
        memset(b, 0, sizeof b);

        a[0].x = b[0].x = 1;
        _for(i, 1, n)
        {
            int x; scanf("%d", &x);
            a[x].x = b[x].x = 1;                                   //a[i].x表示x^i项的系数
        }
        m = n = 2e5;                                              //n表示a多项式的次数 m是b多项式的次数

        solve();

        int cnt = 0;
        scanf("%d", &m);
        while(m--)
        {
            int x; scanf("%d", &x);
            cnt += (ans[x] > 0);
        }
        printf("%d\n", cnt);
    }

    return 0;
}

周日

B. Connecting Universities(贪心)

这题秒啊

我只能想到配对时要尽可能两端的配对,然后就没什么想法了

其实直接想点的配对很难想,考虑配对方案很难想

换一种想法,考虑一条边对答案的贡献。

贪心来讲,一条边左边有x个大学,右边有y个大学,那么它的最大贡献显然是min(x, y)

每条边都这么贪心,就是正确答案,很妙。

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 2e5 + 10;
int a[N], n, k;
vector<int> g[N];
ll ans;

void dfs(int u, int fa)
{
    for(int v: g[u])
    {
        if(v == fa) continue;
        dfs(v, u);
        a[u] += a[v];
        ans += min(a[v], 2 * k - a[v]);
    }
}

int main()
{
    scanf("%d%d", &n, &k);
    _for(i, 1, 2 * k)
    {
        int x; scanf("%d", &x);
        a[x] = 1;
    }
    _for(i, 1, n - 1)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }

    dfs(1, 0);
    printf("%lld\n", ans);

    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值