A. 极源流体

上和下,左和右是等效的,只考虑下和右。

操作顺序不影响结果,按任意顺序操作x次右,y次下后,一个黑格一定会变成一个长为x,宽为y的矩形。可以用两个队列记录位置,这样可以把每一步单独拿出来,枚举x,找到最小的合法的y更新答案。

TLE 79

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 750;
typedef pair<int, int> pii;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

int cnt, pnt, n, m, ans;
int f[maxn*maxn], pf[maxn*maxn];
queue<pii> q, Q, p, P;
bool pcol[maxn][maxn], col[maxn][maxn];
char c[maxn][maxn];
int dx[4] = {0, -1, -1, -1};
int dy[4] = {-1, -1, 0, 1};
int dw[3] = {-1, 0, 1};
bool check(int x, int y) {return x > 0 && y > 0 && x <= n && y <= m;}
int id(int x, int y) {return (x-1)*m+y;}
int fa(int x) {return f[x] == x ? x : f[x] = fa(f[x]);}
int merge(int x, int y) {x = fa(x), y = fa(y); if(x == y) return false; f[y] = x; return true;}
int pa(int x) {return pf[x] == x ? x : pf[x] = pa(pf[x]);}
int pmerge(int x, int y) {x = pa(x), y = pa(y); if(x == y) return false; pf[y] = x; return true;}

void sol(int down)
{
    while(!q.empty()) q.pop(); while(!Q.empty()) Q.pop();
    cnt = pnt;
    for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) col[i][j] = pcol[i][j];
    for(int i=1; i<=n*m; i++) f[i] = pf[i];
    for(int i=1; i<=n; i++) for(int j=1; j<m; j++) if(col[i][j] && !col[i][j+1]) q.push(pii(i, j));
    int nans = down;
    for(int i=1; i<=m; i++)
    {
        if(cnt <= 1) break;
        nans++;
        while(!q.empty())
        {
            pii now = q.front(); q.pop();
            if(now.second == m - 1) continue;
            if(now.second < m - 1)
            {
                for(int j=0; j<3; j++)
                {
                    int nx = now.first + dw[j], ny = now.second + 2;
                    if(col[nx][ny] && check(nx, ny) && merge(id(now.first,now.second), id(nx,ny))) cnt--;
                }
            }
            if(col[now.first][now.second+1] == 0)
            {
                Q.push(pii(now.first, now.second+1));
                col[now.first][now.second+1] = 1;
                merge(id(now.first, now.second), id(now.first, now.second+1));
            }
        }
        swap(Q, q);
    }
    if(cnt == 1) ans = min(ans, nans);
}

int main()
{
    freopen("fluid.in", "r", stdin);
    freopen("fluid.out", "w", stdout);
    
    n = read(); m = read();
    for(int i=1; i<=n; i++) scanf("%s", c[i]+1);
    for(int i=1; i<=n*m; i++) pf[i] = i;
    for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) pcol[i][j] = c[i][j]-'0';
    for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) if(pcol[i][j])
    {
        pnt++;
        for(int k=0; k<4; k++)
        {
            int nx = i + dx[k], ny = j + dy[k];
            if(pcol[nx][ny] && check(nx, ny) && pmerge(id(i,j), id(nx,ny))) pnt--;
        }
    }
    for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) if(pcol[i][j]) p.push(pii(i, j));
    ans = n + m;
    for(int i=0; i<=n && i<ans; i++)
    {
        sol(i);
        while(!p.empty())
        {
            pii now = p.front(); p.pop();
            if(now.first == n) continue;//为什么上面是m-1这里不是n-1??
            if(now.first < n - 1)
            {
                for(int j=0; j<3; j++)
                {
                    int nx = now.first + 2, ny = now.second + dw[j];
                    if(pcol[nx][ny] && check(nx, ny) && pmerge(id(now.first, now.second), id(nx, ny))) pnt--;
                }
            }
            //差别是,上面的染色只用于当前,而当前又用不到,所以没有用
            //可是这个染色是有用的
            if(pcol[now.first+1][now.second] == 0)
            {
                P.push(pii(now.first+1, now.second));
                pcol[now.first+1][now.second] = 1;
                pmerge(id(now.first, now.second), id(now.first+1, now.second));
            }
        }
        swap(P, p);
    }
    printf("%d\n", ans);

    return 0;
}

题意转化:

对每个黑格子建一个点,两个黑格子之间连一条边(优化就是只连有可能最近的两个),这条边的两个权值分别是横纵坐标差。求一组(x, y)使得所有a<=x,b<=y的边能把整张图联通且x+y最小。把合法的边按照横坐标差分组,枚举x,y,找到y组的元素中x满足条件的边加上(就是把所有a<=x,b<=y的边加上),重新枚举的时候还原并查集。(方法是把每个点的father记录下来)

code
 #include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 750;

typedef pair<int, int> pii;
char c[maxn][maxn];
int n, m, ans, f[maxn*maxn], tot, cnt;
vector<int> H[maxn], L[maxn], le;
int id(int x, int y) {return (x-1)*m+y;}
int fa(int x) {return f[x] == x ? x : f[x] = fa(f[x]);}
void merge(int x, int y) {x = fa(x), y = fa(y); if(x != y) f[y] = x, cnt--;}
struct edge{int a, b, x, y;} d[maxn*maxn*2];
vector<edge> v[maxn];
bool cmp(const edge &x, const edge &y) {return x.x == y.x ? x.y < y.y : x.x < y.x;}

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

int main()
{
    freopen("fluid.in", "r", stdin);
    freopen("fluid.out", "w", stdout);
    
    n = read(); m = read();
    for(int i=1; i<=n; i++) scanf("%s", c[i]+1);
    for(int i=1; i<=n; i++) for(int j=1; j<=m; j++)
    {
        if(c[i][j] == '1') H[i].push_back(j), L[j].push_back(i);
    }
    for(int i=1; i<=n; i++)
    {
        for(int x : H[i])
        {
            for(int j=x; j<=m; j++)
            {
                if(c[i][j] == '1' && j != x) break;//就因为去掉这个RE了?
                int p = upper_bound(L[j].begin(), L[j].end(), i)-L[j].begin();
                if(p >= L[j].size()) continue;
                p = L[j][p];
                d[++tot] = {id(i,x), id(p,j), p-i-1, j-x-1};
            }
            for(int j=x-1; j>=1; j--)
            {
                if(c[i][j] == '1') break;
                int p = upper_bound(L[j].begin(), L[j].end(), i)-L[j].begin();
                if(p >= L[j].size()) continue;
                p = L[j][p];
                d[++tot] = {id(i,x), id(p,j), p-i-1, x-j-1};
            }
        }
        for(int j=0; j+1<H[i].size(); j++) d[++tot] = {id(i,H[i][j]), id(i,H[i][j+1]), -1, H[i][j+1]-H[i][j]-1};
    }
    sort(d+1, d+1+tot, cmp);
    for(int i=1; i<=n*m; i++) f[i] = i;
    //两项都<=0那不就是同一个点的意思吗?
    for(int i=1; i<=tot; i++) if(d[i].x<=0 && d[i].y<=0) merge(d[i].a, d[i].b);
    for(int i=1; i<=tot; i++)
    {
        d[i].a = fa(d[i].a), d[i].b = fa(d[i].b);
        if(d[i].a != d[i].b) v[d[i].y+2].push_back(d[i]);
    }
    for(int i=1; i<=n; i++) for(int j=1; j<=m; j++)
    {
        int d = id(i, j); if(fa(d) == d && c[i][j] == '1') le.push_back(d);
    }
    int ans = 0x3f3f3f3f;
    //对每个点找出不同横坐标差下,纵坐标差最小的点建两个权值的边,然后枚举向下走的距离加边,统计答案
    for(int i=0; i<=n; i++)
    {
        for(int x : le) f[x] = x;//还原初始状态
        cnt = le.size();
        for(int j=1; j<=m+2; j++)
        {
            for(edge x : v[j]) if(x.x <= i) merge(x.a, x.b);
            if(cnt == 1) {ans = min(ans, i+max(0,j-2)); break;}
            if(i+max(0, j-2) > ans) break;
        }
    }
    printf("%d\n", ans);

    return 0;
}

 

B. 等差数列

我连直接枚举公差都没想到……

对于一个公差,每个数都有一个使它不变的首项,不变的个数越多越好,找到出现最多的当首项就是当前公差下的最优解。

细节是及时排除不可能的首项防止数组越界。

code
 // ubsan: undefined
// accoders
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 271900;

int n, w, a[maxn], ans, res, d, vis[maxn], mx;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

int main()
{
    freopen("sequence.in", "r", stdin);
    freopen("sequence.out", "w", stdout);
    
    n = read(); w = read(); bool sp = 1;
    for(int i=1; i<=n; i++) 
    {
        a[i] = read();
        if(a[i] != 1) sp = 0;
    }
    if(sp) 
    {
        printf("0\n"); exit(0);
    }
    ans = n - 1;
    d = w / (n-1);
    for(int i=0; i<=d; i++)
    {
        res = 0; mx = 0;
        for(int j=1; j<=n; j++)
        {
            int x = a[j]-i*(j-1);
            if(x <= 0 || a[j]+i*(n-j)>w) continue;
            vis[x]++;
            if(vis[x] > vis[mx]) mx = x;
        }
        for(int j=1; j<=n; j++)
        {
            if(a[j] != mx) res++;
            mx += i;
            int x = a[j]-i*(j-1); 
            if(x <= 0 || a[j]+i*(n-j)>w) continue;
            vis[x] = 0;//忽然发现没清空??
        }
        ans = min(ans, res);
    }
    printf("%d\n", ans);

    return 0;
}

 

C. 送给好友的礼物

枚举二进制数有77分。

dp很神奇, 解释在注释里

code
 #include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 447;

int n, k, si[maxn], key[maxn], is[maxn], dis[maxn][maxn];
short f[3][maxn][maxn+maxn];
vector<int> g[maxn], v;

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

void dfs(int x, int fa, int root)
{
    for(int v : g[x])
    {
        if(v == fa) continue;
        dis[root][v] = dis[root][x] + 1;
        dfs(v, x, root);
    }
}

void sol(int x, int fa)
{
    for(int v : g[x])
    {
        if(v == fa) continue;
        sol(v, x);
        si[x] += si[v];
    }
    if(is[x] && !si[x]) v.push_back(x);
    si[x] += is[x];
}

void Ckmi(short &x, short y) {x = x < y ? x : y;}
inline int min(int x, int y) {x = x < y ? x : y; return x;}
//既然加多少强制类型转换都不让我过编译,那我就不用了qwq

int main()
{
    freopen("gift.in", "r", stdin);
    freopen("gift.out", "w", stdout);
    
    n = read(); k = read();
    for(int i=1; i<n; i++)
    {
        int u = read(), v = read();
        g[u].push_back(v); g[v].push_back(u);
    }
    for(int i=1; i<=k; i++) is[key[i] = read()] = 1;
    for(int i=1; i<=n; i++) dfs(i, 0, i);
    if(k == 1) {printf("%d\n", dis[1][key[1]]*2); exit(0);}
    //1又不是叶子,把1放进来干什么??
    //因为可能所有的叶子都和当前讨论的叶子同组,另一组为空,计算路径时就从起点开始走
    //当k!=1时它不会是最终答案,但可以是中间过程
    v.push_back(1); sol(1, 0);
    memset(f, 0x3f, sizeof(f));
    f[1][0][dis[1][v[1]]] = 0;
    int s = v.size();
    for(int i=1; i<s-1; i++)
    {
        for(int j=0; j<i; j++)
        {
            for(int t=0; t<=n+n; t++) if(f[i&1][j][t] != f[0][0][0])
            {
                //这时加入第1个叶子。第i+1个叶子还不考虑
                //第i个叶子变成了2组的最后一个,如果i+1在2组,2组的答案是new f,1组的答案是t
                Ckmi(f[(i+1)&1][i][f[i&1][j][t]+dis[v[j]][v[i+1]]], t);
                //第i个叶子变成了1组的最后一个,如果i+1在2组,2组的答案是f[i&1][j][t],1组的答案是new t
                Ckmi(f[(i+1)&1][j][t+dis[v[i]][v[i+1]]], f[i&1][j][t]);
                //其实1组二组什么的只是在讨论第i+1个叶子到底是不是和i一组,第二维就是不和i+1一组的最后一个
                //f的值一直是第一维所属组的答案,1组和2组是相对的
                f[i&1][j][t] = f[0][0][0];
            }
        }
    }
    short ans = f[0][0][0];
    for(short i=0; i<s-1; i++)//s-1(最后一个叶子)所属组外另一个组的最后一个
    {
        for(short j=0; j<=min((short)n+n, ans); j++)
        {
            ans = min(ans, (short)max(j+dis[v[s-1]][1], f[(s-1)&1][i][j]+dis[v[i]][1]));
        }
    }
    printf("%d\n", ans);

    return 0;
}

 

D. 非常困难的压轴题

题解是枚举中间的W前后做乘法。

其实对L,W,和S都可以考虑枚举它前面的数,所以每个字母合法的个数都是它上一级的字母个数的和,所以加两个前缀和。

code
 #include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int maxn = 145000;

int n;
ll res, fis, ans;
char s[maxn];

inline int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}

int main()
{
    freopen("lws.in", "r", stdin);
    freopen("lws.out", "w", stdout);
    
    scanf("%s", s+1);
    n = strlen(s+1);
    for(int i=1; i<=n; i++)
    {
        if(s[i] == 'L') res++;
        else if(s[i] == 'W') fis += res;
        else if(s[i] == 'S') ans += fis;
    }
    printf("%lld\n", ans);

    return 0;
}

原文地址:http://www.cnblogs.com/Catherine2006/p/16868073.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性