20240520刷题总结

T1(状态置换,搜索与dp, dp存值结构体)

T376。
还是从搜索角度去考虑:时间,前i物品,最多拿多少。
这样我们去设计状态,我一开始设置:时间,前i,值是拿多少。会发现这样会爆。
其实换一下,优化效果更好。前i物品,最多拿j,用的最少时间。
实际转移就是背包。存值就存结构体。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2010, INF = 0x3f3f3f3f;

struct Node
{
    int day, tim;
    bool operator< (const Node& W) const
    {
        if (day != W.day) return day < W.day;
        return tim < W.tim;
    }
}f[N];
int n, t, m;

Node add(Node a, int b)
{
    if (b > t) return {INF, INF}; //一天都放不下
    Node c = {a.day, a.tim + b};
    if (c.tim > t) c.day ++ , c.tim = b;
    return c;
}

int main()
{
    scanf("%d%d%d", &n, &t, &m);
    
    for (int i = 1; i <= n; i ++ ) f[i] = {INF, INF};
    f[0] = {1, 0}; //第1天
    for (int i = 1; i <= n; i ++ )
    {
        int v;
        scanf("%d", &v);
        for (int j = i; j; j -- )
        {
            f[j] = min(f[j], add(f[j - 1], v));
        }
    }
    
    for (int i = n; i; i -- )
    {
        if (f[i].tim <= t && f[i].day <= m)
        {
            printf("%d\n", i);
            return 0;
        }
    }
    puts("0");
    
    return 0;
}

T2(数学题)

1949。
s->t, 首先构造方案。先往下走,走到不能走乘2,一直加,加到不能加乘,乘完减回来。

唯一有疑问的是乘完往不往下走1。假设乘完往下走,乘二至少剩下1个格。

中间大于等于一,因为最坏情况也是偶数。如果起点为奇数,那么数为2* (n - 1), 减n,是n - 2, 而n是严格>=2的。所以最起码不会更坏。
也就是1 + 1 <= (>=1) + 1
这样就行了。最后跳直接枚举就行了。 k * 2 - n + 1, (k + m) * 2 - n + m。其实也一样。

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

int n, k, ans = 0;

int main() {
	cin >> n >> k;
	
	if (n >= k)
		ans = n-k;
	else {
		int i=n, step=0;
		for (;i<k;) {p
			if (i * 2 > k)
				ans = max(ans, step + 1 + i*2-k);
				
			i++;
			if (i % 2 == 0 && i/2 < n)
				step = max(step+1, n-i/2 + 1);
			else
				step = step+1;
		}
		ans = max(ans, step);
	}
	
	cout << ans;
	
	return 0;
}

T3:(回文,重新分类,dp套小dp,两个相互加中间中转)

2579
首先回文问题,从中间或两端走,这里从中间bfs。其实这个好想。

每次扩展相同字母。
其实极端情况能卡爆。就是每个都是a扩展。
这样就过不去了。考虑优化。这里两个相互加,我们可以考虑在中间加一个中转,这样不就行了?如何套呢?先扩展一条边的状态就行了,再扩展。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 410, M = 30, K = 60010;


int h[N], pre_h[N], e[K << 1], ne[K << 1], w[K << 1], idx;

bool flag[N][N];
int f[N][N], g[N][N][M];
int c[N];
struct Node
{
    int x, y, c;
}q[5000010];
int n, m;

void add(int h[], int a, int b, int c)
{
    e[ ++ idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx;
}

void bfs()
{
    memset(f, 0x3f, sizeof f);
    memset(g, 0x3f, sizeof g);
    int hh = 0, tt = -1;
    for (int i = 1; i <= n; i ++ ) q[ ++ tt] = {i, i, 0}, f[i][i] = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= n; j ++ )
            if (i != j && flag[i][j]) q[ ++ tt] = {i, j, 0}, f[i][j] = 1;
    
    while (hh <= tt)
    {
        Node t = q[hh ++ ];
        
        if (!t.c) //f
        {
            for (int i = h[t.y]; i; i = ne[i])
            {
                int j = e[i];
                if (g[t.x][j][w[i]] > f[t.x][t.y] + 1)
                {
                    g[t.x][j][w[i]] = f[t.x][t.y] + 1;
                    q[ ++ tt] = {t.x, j, w[i]};
                }
            }
        }
        else
        {
            for (int i = pre_h[t.x]; i; i = ne[i])
            {
                int j = e[i];
                if (t.c != w[i]) continue;
                if (f[j][t.y] > g[t.x][t.y][t.c] + 1)
                {
                    f[j][t.y] = g[t.x][t.y][t.c] + 1;
                    q[ ++ tt] = {j, t.y, 0};
                }
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b;
        char c;
        scanf("%d %d %c", &a, &b, &c);
        flag[a][b] = true; //单向边
        add(h, a, b, (int)c - 'a' + 1), add(pre_h, b, a, (int)c - 'a' + 1);
    }
    
    bfs();
    
    int k;
    scanf("%d", &k);
    for (int i = 0; i < k; i ++ )
    {
        scanf("%d", &c[i]);
        if (i)
        {
            if (f[c[i - 1]][c[i]] < 1e9) printf("%d\n", f[c[i - 1]][c[i]]);
            
            else puts("-1");
        }
    }
    
    return 0;
}

T4(枚举,spfa要快一些)

T329。
枚举最短路上边边,跑最短路。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1010, M = N * N;

int h[N], e[M], ne[M], w[M], idx;
int pre[N], prew[N];
bool st[N];
int d[N];
int n, m;
bool flag;
void add(int a, int b, int c)
{
    e[ ++ idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx;
}

void spfa(int del)
{
    queue<int> q;
    flag = false;
    memset(d, 0x3f, sizeof d);
    d[1] = 0;
    q.push(1);
    
    while (q.size())
    {
        int t = q.front(); q.pop();
        st[t] = false;
        
        for (int i = h[t]; i; i = ne[i])
        {
            int j = e[i];
            if (i == del) continue;
            if (d[j] > d[t] + w[i])
            {
                pre[j] = t;
                prew[j] = i;
                d[j] = d[t] + w[i];
                if (!st[j]) q.push(j), st[j] = true;
            }
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    
    spfa(0);
    int ans = d[n];
    int k = n, w = prew[n];
    while (k)
    {
        spfa(w);
         w = prew[k];
         k = pre[k];
         ans = max(ans, d[n]);
    }
    cout << ans << endl;
    
    return 0;
}

T5(分治:很值得看)

388。
精髓在于处理两点之间的权值。分治,直到两点在一块。不过具体怎么做没想清楚。几何推理法不在阐述,自己看代码。总体就是暴力交点。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std;

const int N = 110, M = 2010;

struct Node { int x, y; } point[N], station[N];
int n, m, k;
int g[N][N];
bool st[N], used[N];
int dist[N];

double get_dist(Node a, Node b)
{
    int dx = a.x - b.x;
    int dy = a.y - b.y;
    return sqrt(dx * dx + dy * dy);
}

int find(Node x)
{
    double res = 0x3f3f3f3f, id = 0;
    for (int i = 1; i <= k; i ++ )
    {
        if (get_dist(station[i], x) < res)
        {
            res = get_dist(station[i], x);
            id = i;
        }
    }
    return id;
}

int get(Node a, Node b)
{
    int res = 0;
    int sta = find(a), stb = find(b);
    if (sta == stb) return 0;
    
    Node c = {(a.x + b.x) >> 1, (a.y + b.y) >> 1};
    int stc = find(c);
    st[sta] = st[stb] = st[stc] = true;
    
    if (sta != stc) get(a, c);
    if (stb != stc) get(b, c);
    for (int i = 1; i <= k; i ++ )
        if (st[i]) res ++ ;
    return res - 1;
}

void dijkstra()  // 求1号点到n号点的最短路距离
{
    memset(dist, 0x3f, sizeof dist);
    dist[1] = 0;

    for (int i = 1; i <= n; i ++ )
    {
        int t = -1;
        for (int j = 1; j <= n; j ++ )
            if (!used[j] && (t == -1 || dist[t] > dist[j]))
                t = j;
        for (int j = 1; j <= n; j ++ )
            if (g[t][j]) dist[j] = min(dist[j], dist[t] + g[t][j]);
    }
    return;
}


int main()
{
    scanf("%d%d%d", &k, &n, &m);
    for (int i = 1; i <= k; i ++ ) scanf("%d%d", &station[i].x, &station[i].y);
    for (int i = 1; i <= n; i ++ ) scanf("%d%d", &point[i].x, &point[i].y);
    
    memset(g, 0x3f, sizeof g);
    while (m -- )
    {
        memset(st, 0, sizeof st);
        int a, b;
        scanf("%d%d", &a, &b);
        g[b][a] = g[a][b] = get(point[a], point[b]);
    }
    
    dijkstra();
    
    for (int i = 1; i <= n; i ++ )
    {
        if (dist[i] > 1e9) puts("-1");
        else printf("%d\n", dist[i]);
    }

    return 0;
}

最开始的代码。显然不对。(double呢?)
下次就不要犯了。

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

int B, C, R;
struct Point {
	double x, y;
}pb[105], pc[105];
int mapp[105][105];

bool flag[105] = {false};

int cover(double x, double y) {
	int idx = 1, dis = (x - pb[1].x) * (x - pb[1].x) + (y - pb[1].y) * (y - pb[1].y);
	for (int i=2; i<=B; i++) {
		if ((x - pb[i].x) * (x - pb[i].x) + (y - pb[i].y) * (y - pb[i].y) < dis) {
			idx = i;
			dis = (x - pb[i].x) * (x - pb[i].x) + (y - pb[i].y) * (y - pb[i].y);
		}
	}
	
	return idx;
}

void dfs(double x1, double y1, int coverA, double x2, double y2, int coverB) {
	if ((x1-x2) * (x1-x2) + (y1-y2) * (y1-y2) <= 1e-8)
		return;
	
	double midx = (x1+x2)/2;
	double midy = (y1+y2)/2;
	
	int coverC = cover(midx, midy);
	flag[coverC] = true;
	
	if (coverA != coverC) {
		dfs(x1, y1, coverA, midx, midy, coverC);
	}
	
	if (coverB != coverC) {
		dfs(midx, midy, coverC, x2, y2, coverB);
	}
}

int calc(int u, int v) {
	for (int i=1; i<=B; i++)
		flag[i] = false;
		
	int coverA = cover(pc[u].x, pc[u].y);
	int coverB = cover(pc[v].x, pc[v].y);
	flag[coverA] = flag[coverB] = true;
	
	if (coverA != coverB)
		dfs(pc[u].x, pc[u].y, coverA, pc[v].x, pc[v].y, coverB);
	
	int ans = 0;
	for (int i=1; i<=B; i++) {
		if (flag[i])
			ans++;
	}
	return ans-1;
}

int dis[105];
bool vis[105] = {false};

void dij() {
	for (int i=1; i<=C; i++) {
		dis[i] = 1000000;
	}
	
	dis[1] = 0;
	
	for (int i=1; i<=C; i++) {
		int idx = 0, m = 1000005;
		for (int j=1; j<=C; j++) {
			if (!vis[j] && dis[j] < m) {
				idx = j;
				m = dis[j];
			}
		}
		vis[idx] = true;
		
		for (int j=1; j<=C; j++) {
			if (mapp[idx][j] != -1 && !vis[j])
				dis[j] = min(dis[j], dis[idx] + mapp[idx][j]);
		}
	}
}

int main() {
	cin >> B >> C >> R;
	for (int i=1; i<=B; i++) {
		cin >> pb[i].x >> pb[i].y;
	}
	for (int i=1; i<=C; i++) {
		cin >> pc[i].x >> pc[i].y;
	}
	
	for (int i=1; i<=C; i++)
		for (int j=1; j<=C; j++)
			mapp[i][j] = -1;
	
	for (int i=1; i<=R; i++) {
		int u, v;
		cin >> u >> v;
		if (u != v && mapp[u][v] == -1) {
			mapp[u][v] = calc(u, v);
			mapp[v][u] = mapp[u][v];
		}	
	}
	
	dij();
	
	for (int i=1; i<=C; i++) {
		if (dis[i] == 1000000)
			cout << -1 << endl;
		else
			cout << dis[i] << endl;
	}
	
	return 0;
} 

看了这份,我神清气爽。原来是这样:直接分治然后标记就行了。在最大的那个函数里面统计标记个数。这么简单,记住吧。

T6(二分好题,基础算法,区间和转前缀和,分数规划)

拿到这个题,哇,好复杂,>=m就中区间sum/l的最大值。
发现了什么?????从另一个角度看,竟然是分数规划。(我刚发现)。(二分课件)。每个点俩权值,一个在上面(a[i]),一个在下面(1)。
按原来的思路吧。二分答案。变化一下。存不存在长度>=m的区间 ,sum>= mid * l,也就是很简单了。移项后等价于(sum = a[l] + .. + a[r] 然后移过来一个mid,)每个点sum-mid,问存不存在>=0的长度>=m的区间。 直接枚举每个点(将所有区间按照右端点分类),然后其实是前缀min.

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1000010;

int n, m;
int h[N];
double s[N];

bool check(double mid) //是否存在length>=m的区间和>=l * mid
{
    double minv = 0, res = -0x3f3f3f3f;
    s[0] = 0;
    for (int i = 1; i <= n; i ++ )
    {
        s[i] = s[i - 1] + h[i] - mid;
        if (i >= m) minv = min(minv, s[i - m]);
        if (i >= m) res = max(res, s[i] - minv);
    }
    return res >= 0;
}

int main()
{
    scanf("%d%d", &n, &m);
    double r = 0;
    for (int i = 1; i <= n; i ++ ) scanf("%d", &h[i]), r = max(r, (double)h[i]);
    
    double l = 0;
    while (r - l > 1e-5)
    {
        double mid = (l + r) / 2;
        if (check(mid)) l = mid;
        else r = mid;
    }
    printf("%lld", (long long)(r * 1000));
    return 0;
}
posted @ 2024-05-21 08:01  hhhhhua  阅读(56)  评论(0)    收藏  举报