暑假第四周

写日报真的是很好的督促学习的方式

暑假以来比较浪,从今天开始改变自己

周一

Concatenation(字符串比较)

暴力排序竟然可以过……

显然难点在于前缀相同的情况,这个时候比较ab和ba即可

#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 = 2e6 + 10;
string a[N];

bool cmp(string x, string y)
{
    int len = min(x.size(), y.size());
    rep(i, 0, len)
        if(x[i] != y[i])
            return x[i] < y[i];
    return x + y < y + x;
}

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

    sort(a + 1, a + n + 1, cmp);
    _for(i, 1, n) cout << a[i]; 
    puts("");

    return 0;
}

Ancestor(前缀后缀+代码技巧)

比赛时分类讨论去了,其实是弄复杂了

一个区间去掉一个点,就剩下前缀和后缀了,于是我们可以预处理出前缀lca和后缀lca

因为同样的代码要写两份,所以写个结构体可以大大简化代码

#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 = 1e5 + 10;
int k[N], n, m;

struct Tree
{
    int d[N], up[N][20], q[N], h[N], val[N];
    vector<int> g[N];

    void read()
    {
        _for(i, 1, n) scanf("%d", &val[i]);
        _for(i, 2, n)
        {
            int x; scanf("%d", &x);
            g[x].push_back(i);
        }
    }

    void dfs(int u, int fa)
    {
        d[u] = d[fa] + 1;
        up[u][0] = fa;
        _for(j, 1, 19) up[u][j] = up[up[u][j - 1]][j - 1];

        for(int v: g[u])
        {
            if(v == fa) continue;
            dfs(v, u);
        }
    }

    int lca(int u, int v)
    {
        if(!u || !v) return u + v;
        if(d[u] < d[v]) swap(u, v);
        for(int j = 19; j >= 0; j--)
            if(d[up[u][j]] >= d[v])
                u = up[u][j];
        if(u == v) return u;
        for(int j = 19; j >= 0; j--)
            if(up[u][j] != up[v][j])
                u = up[u][j], v = up[v][j];
        return up[u][0];
    }

    void init()
    {
        dfs(1, 0);
        q[1] = k[1];
        _for(i, 2, m) q[i] = lca(q[i - 1], k[i]);
        h[m] = k[m];
        for(int i = m - 1; i >= 1; i--) h[i] = lca(h[i + 1], k[i]);
    }

    int cal(int x)
    {
        return val[lca(q[x - 1], h[x + 1])];
    }

}A, B;

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, m) scanf("%d", &k[i]);
    A.read(); A.init();
    B.read(); B.init();

    int ans = 0;
    _for(i, 1, m)
        if(A.cal(i) > B.cal(i))
            ans++;
    printf("%d\n", ans);
    
    return 0;
}

Journey(0/1bfs)

边看作点,路口看作边建图,边的权值只有0和1,这时用0/1bfs即可,和bfs复杂度一样

注意一些细节,建图配id的时候,注意用unordered_map

0/1bfs和bfs的区别在于用双端队列,边权为0的点放前面,否则放后面

用松弛操作来更新,这样一个点最多入队两次。然后不需要vis数组

此外,第一次找到的点不一定是最优的,但第一次出队的点一定是最优的

其实0/1bfs就是一个特殊的dijsktra,只是说优先队列变成了双端队列,通过入队方式来保证单调性。

#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 = 5e5 * 8 + 10;
unordered_map<ll, int> mp;
vector<pair<int, int>> g[N];
int d[N], vis[N], n, cnt;

int id(int x, int y)
{
    ll t = 1LL * x * 1e6 + y;
    if(!mp[t]) mp[t] = ++cnt;
    return mp[t];
}

int solve(int st, int dest)
{
    _for(i, 1, cnt) d[i] = 1e9;
    d[st] = 0;
    deque<int> q;                 //0/1bfs用双端队列
    q.push_back(st);

    while(!q.empty())
    {
        int u = q.front(); q.pop_front();
        if(u == dest) return d[u];            //0/1bfs中,出队的时候是最小值,第一次遇到不一定是最小值
        for(auto x: g[u])
        {
            int v = x.first, w = x.second;
            if(d[v] > d[u] + w)               //松弛操作
            {
                d[v] = d[u] + w;
                if(!w) q.push_front(v);       //0放队首 1放队尾
                else q.push_back(v);
            }
        }
    }
    return -1;
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int c[5];
        _for(j, 1, 4) scanf("%d", &c[j]);
        _for(j, 1, 4)
        {
            if(!c[j]) continue;
            g[id(c[j], i)].push_back({id(i, c[j]), 1});
            int ii = j % 4 + 1;
            if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 0});
            ii = (j + 1) % 4 + 1;
            if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 1});
            ii = (j + 2) % 4 + 1;
            if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 1});
        }
    }

    int s1, s2, t1, t2;
    scanf("%d%d%d%d", &s1, &s2, &t1, &t2);
    int st = id(s1, s2), dest = id(t1, t2);
    printf("%d\n", solve(st, dest));

    return 0;
}

这题直接dijsktra也可以过

当然要注意,第一次入队不一定是最优的,第一次出队才是最优的。或者干脆算法跑完后再输出。

#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 = 5e5 * 8 + 10;
unordered_map<ll, int> mp;
int d[N], vis[N], n, cnt;
struct node
{
    int v, w;
    bool operator < (const node& rhs) const
    {
        return w > rhs.w;
    }
};
vector<node> g[N];

int id(int x, int y)
{
    ll t = 1LL * x * 1e6 + y;
    if(!mp[t]) mp[t] = ++cnt;
    return mp[t];
}

int solve(int st, int dest)
{
    _for(i, 1, cnt) d[i] = 1e9;
    d[st] = 0;
    priority_queue<node> q;
    q.push({st, d[st]});

    while(!q.empty())
    {
        node x = q.top(); q.pop();
        int u = x.v;
        if(u == dest) return d[u];
        if(d[u] != x.w) continue;

        for(auto x: g[u])
        {
            int v = x.v, w = x.w;
            if(d[v] > d[u] + w)
            {
                d[v] = d[u] + w;
                q.push({v, d[v]});
            }
        }
    }
    return -1;

}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n)
    {
        int c[5];
        _for(j, 1, 4) scanf("%d", &c[j]);
        _for(j, 1, 4)
        {
            if(!c[j]) continue;
            g[id(c[j], i)].push_back({id(i, c[j]), 1});
            int ii = j % 4 + 1;
            if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 0});
            ii = (j + 1) % 4 + 1;
            if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 1});
            ii = (j + 2) % 4 + 1;
            if(c[ii]) g[id(c[j], i)].push_back({id(i, c[ii]), 1});
        }
    }

    int s1, s2, t1, t2;
    scanf("%d%d%d%d", &s1, &s2, &t1, &t2);
    int st = id(s1, s2), dest = id(t1, t2);
    printf("%d\n", solve(st, dest));

    return 0;
}

周二

UVA1723 Intervals(差分约束)

这是差分约束的裸题

差分约束就是有n个变量,m个不等式,然后求某个变量的最值

比如x1 - x2 >= 2

转化一下为x1 >= x2 + 2

这个式子类似于最短路里面的dis1 <= dis2 + w

如果换成最长路就有dis1 >= dis2 + w

因此差分约束就是建图然后跑最长路,对于这个式子就是2号点向1号点连一条长度为2的边

对于这道题而言,转化为前缀和的形式可以得到m个等式

但是注意还有一些隐藏条件,一个点要不有要不没有,所以1 >= di - di-1 >= 0

这样子建图完之后,还有一些细节

比如这道题从0开始,但是有i-1,于是我们可以把所有输入+1,就没有这个问题

然后,这样子其实只是求得各种不等式,如果变量要取值,必须有1个变量要取一个具体的值

于是我们设d[0] = 0, 也就是这个变量取了0,然后其他变量都能推出来了。

除了0以外其他变量要设为最小值,不要设为0

#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 = 5e4 + 10;
int d[N], vis[N], n, mx;
vector<pair<int, int>> g[N];

int spfa()
{
    _for(i, 1, mx) d[i] = -1e9;       //求最长路的时候 这个地方要反过来设为最小值,不能设为0
    queue<int> q;
    q.push(0);
    vis[0] = 1;
    while(!q.empty())
    {
        int u = q.front(); q.pop();
        vis[u] = 0;
        for(auto t: g[u])
        {
            int v = t.first, w = t.second;
            if(d[v] < d[u] + w)      //松弛操作注意不等号方向
            {
                d[v] = d[u] + w;
                if(!vis[v]) 
                {
                    vis[v] = 1;
                    q.push(v);
                }
            }
        }
    }
    return d[mx];
}

int main()
{
    int T, fi = 1; scanf("%d", &T);
    while(T--)
    {
        if(fi) fi = 0;
        else puts("");
        mx = 0;
        scanf("%d", &n);
        _for(i, 1, n)
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            a++; b++;
            g[a - 1].push_back({b, c});
            mx = max(mx, b);
        }
        _for(i, 1, mx) 
        {
            g[i - 1].push_back({i, 0});
            g[i].push_back({i - 1, -1});
        }
        printf("%d\n", spfa());
        _for(i, 0, mx) g[i].clear();
    }

    return 0;
}

Link with Level Editor I(bfs+省空间)

这题很容易想到建图bfs,但是比赛的时候被空间限制卡住了

怎么省呢?用时间换空间。如果按照正常的存图方式,就会炸空间,要换一种存图方式

用m个vector,每个vector存这个点在所有世界的边,每次查找用二分。对于一定会有的边,即原地不动的情况,这个时候不存到数组里面,而是搜索的时候特殊处理即可

#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 = 1e4 + 10;
const int M = 2e3 + 10;
vector<pair<int, int>> edge[N];
struct node { int c, u, step; };
bitset<M> vis[N];
int n, m;

int solve()
{
    queue<node> q;
    _for(i, 1, n) 
    {
        q.push({i, 1, 1});
        vis[i][1] = 1;
    }
    while(!q.empty())
    {
        node x = q.front(); q.pop();
        int c = x.c, u = x.u, step = x.step;

        int l = lower_bound(edge[u].begin(), edge[u].end(), make_pair(c, 0)) - edge[u].begin();
        int r = upper_bound(edge[u].begin(), edge[u].end(), make_pair(c, (int)1e9)) - edge[u].begin();
        rep(i, l, r)
        {
            int v = edge[u][i].second;
            if(vis[c][v]) continue;
            vis[c][v] = 1;
            if(v == m) return step;
            q.push({c + 1, v, step + 1});
        }
        if(c < n)
        {           
            if(vis[c + 1][u]) continue;
            vis[c + 1][u] = 1;
            q.push({c + 1, u, step + 1});
        }
    }
    return -1;
}

int main()
{
    scanf("%d%d", &n, &m);
    _for(i, 1, n)
    {
        int l; scanf("%d", &l);
        while(l--)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            edge[u].push_back({i, v});
        }
    }
    printf("%d\n", solve());

    return 0;
}

Link with Level Editor I(dp)

这题还有dp的做法,状态设计比较巧妙

设dp[i][j]表示从第k个世界1到达第i个世界j的最大k是多少

初始化dp[i][1] = i + 1

dp[i + 1][v] = max(dp[i][u])

用滚动数组省空间,注意初始化时不合法的状态要设置为最小值

#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 M = 2e3 + 10;
int dp[2][M], n, m;

int main()
{
    scanf("%d%d", &n, &m);

    int ans = 1e9, t = 0;
    _for(i, 1, m) dp[t][i] = -1e9;
    _for(i, 1, n)
    {
        _for(j, 1, m) dp[t ^ 1][j] = dp[t][j];  //先赋值不动的情况
        dp[t][1] = i;
        int l; scanf("%d", &l);
        while(l--)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            dp[t ^ 1][v] = max(dp[t ^ 1][v], dp[t][u]);
            if(v == m) ans = min(ans, i + 1 - dp[t ^ 1][v]);
        }
        t ^= 1;
    }
    printf("%d\n", ans == 1e9 ? -1 : ans);

    return 0;
}

周四

Link with Monotonic Subsequence(Dilworth定理)

可以打表找规律

这题其实用到了一个定理,也就是一序列的最长上升子序列=最小划分的下降子序列。

右边的意思是最少可以把序列划分成多少个下降子序列。这个等式上升下降可以调换,这是一个定理,即Dilworh定理。

n<=lds * lis

也就是说最长下降子序列长度是lds,划分个数又是lis

所以可以得到max(lds, lis) >= 根号n上取整。

所以答案是这个,就可以按照这个答案的长度为一段来划分即可。

#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;

int main()
{ 
    int T; scanf("%d", &T);
    while(T--)
    {
       int n; scanf("%d", &n);
       int t = ceil(sqrt(n));

       vector<int> ve;
       for(int i = n - t + 1; ; i -= t) 
       {
            _for(j, i, i + t - 1)
                if(j >= 1)
                    ve.push_back(j);
            if(i < 1) break;
       }

       for(int x: ve) printf("%d ", x);
       puts("");
    }
   
    return 0;
}

Link with Arithmetic Progression(推公式)

其实就是线性回归,不记得就直接手推

写出出目标函数,然后求偏导为0

比赛中总是犯不该犯的错误,自信点,平时保持训练状态

#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 = 1e5 + 10;
long double y[N], x[N], A, B, ans;

int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        int n; scanf("%d", &n);
        long double a = 0, b = 0, c = 0, d = 0;

        _for(i, 1, n)
        {
            scanf("%Lf", &y[i]);
            x[i] = i;
            a += x[i];
            b += x[i] * x[i];
            c += x[i] * y[i];
            d += y[i];
        }
        B = (a * c - b * d) / (a * a - n * b);
        A = (c - a * B) / b;

        ans = 0;
        _for(i, 1, n)
        {
            long double cur = y[i] - (A * x[i] + B);
            ans += pow(cur, 2);
        }
        printf("%.10Lf\n", ans);
    }

    return 0;
}

周日

Jobs (Easy Version)(前缀和+二进制)

用类似三维前缀和的思想。

因为只有10个公司,这么小的数据可以想到二进制,用二进制优化。

#include <bits/stdc++.h>
#include <random>
#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 = 400 + 10;
const int mod = 998244353;
int a[N][N][N];
int n, q, seed;

int solve(int x, int y, int z)
{
    int res = 0;
    rep(i, 0, n)
        res += (a[x][y][z] >> i) & 1;
    return res;
}

ll binpow(ll a, ll b)
{
    ll res = 1;
    for(; b; b >>= 1)
    {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
    }
    return res;
}

int main()
{ 
    scanf("%d%d", &n, &q);
    rep(i, 0, n)
    {
        int m; scanf("%d", &m);
        while(m--)
        {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            a[x][y][z] |= 1 << i;
        }
    }
    scanf("%d", &seed);
    mt19937 rng(seed);
    uniform_int_distribution<> u(1,400);
    _for(i, 1, 400)
        _for(j, 1, 400)
            _for(k, 1, 400)
                a[i][j][k] = a[i][j][k] | a[i - 1][j][k] | a[i][j - 1][k] | a[i][j][k - 1];

    ll ans = 0;

    int lastans=0;
    for (int i=1;i<=q;i++)
    {
        int IQ=(u(rng)^lastans)%400+1;  // The IQ of the i-th friend
        int EQ=(u(rng)^lastans)%400+1;  // The EQ of the i-th friend
        int AQ=(u(rng)^lastans)%400+1;  // The AQ of the i-th friend
        lastans=solve(IQ,EQ,AQ);  // The answer to the i-th friend

        ans = (ans + lastans * binpow(seed, q - i) % mod) % mod;
    }
    printf("%lld\n", ans);

    return 0;
}

Particle Arts(二进制/数据类型)

可以发现最后稳定的状态肯定是任意两个数的二进制为包含关系

那么将它们排序,就可以得到某一位是一堆1然后是一堆0

又因为这个过程中一位上1的个数是不变的,所以就可以推出最后的结果

那么求方差即可,不要用分数,把式子带进去。

我一开始留着平方没化简,然后爆long long了,后面用__int128过了

其实把平方拆开,可以约去一个n,然后用unsigned long long就不会爆了

unsigned long long版

#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 unsigned long long ull;
const int N = 1e5 + 10;
int num[20], n;
ull a[N];

ull gcd(ull a, ull b) { return !b ? a : gcd(b, a % b); }

int main()
{
    scanf("%d", &n);
    _for(i, 1, n) 
    {
        int x; scanf("%d", &x);
        _for(j, 0, 16)
            if(x & (1 << j))
                num[j]++;
    }

    _for(i, 0, 16)
        _for(j, 1, num[i])
            a[j] |= (1 << i);

    ull sum = 0, p = 0, q = 0;
    _for(i, 1, n) sum += a[i], p += a[i] * a[i];
	p = n *	p - sum * sum;
	q = 1LL * n * n;
   	
   	ull g = gcd(p, q);
	printf("%llu/%llu\n", p / g, q / g);

    return 0;
}

__int128版

#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 = 1e5 + 10;
int num[20], n;
__int128 a[N];

__int128 gcd(__int128 a, __int128 b)
{
	return !b ? a : gcd(b, a % b);
}

void print(__int128 n)
{
	if(n / 10) print(n / 10);
    putchar(n % 10 + '0');
}

int main()
{
    scanf("%d", &n);
    _for(i, 1, n) 
    {
        int x; scanf("%d", &x);
        _for(j, 0, 16)
            if(x & (1 << j))
                num[j]++;
    }

    _for(i, 0, 16)
        _for(j, 1, num[i])
            a[j] |= (1 << i);
    
    __int128 sum = 0, p = 0, q = 0;
    _for(i, 1, n) sum += a[i];
   	_for(i, 1, n) p += (sum - n * a[i]) * (sum - n * a[i]);
   	q = 1LL * n * n * n;
   	
   	__int128 g = gcd(p, q);
	print(p / g);
	putchar('/');
	print(q / g);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值