首页 > 其他分享 >2022NOIPA层联测22

2022NOIPA层联测22

时间:2022-11-08 07:11:18浏览次数:68  
标签:ch 2022NOIPA 22 int long while maxn 联测 getchar

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

标签:ch,2022NOIPA,22,int,long,while,maxn,联测,getchar
From: https://www.cnblogs.com/Catherine2006/p/16868073.html

相关文章

  • NOIP联测22
    T1.极源流体正解是\(LCT\)维护最小生成树,显然我是不会的。性质一:向上和向下等价,向左和向右等价,所以可以只考虑向右下方扩展。性质二:向下和向右操作顺序可以交换,那么可以......
  • 【2022.11.7】luffy项目前期部署(3)
    今日内容1前台全局样式和js配置#bodydiv默认样式,统一去掉#写一个,应用到项目中#后端接口的地址,统一写,以后统一改1.1global.css/*声明全局样式和项目的初......
  • ArchLinux安装手册(2022-10-01)
    准备工作镜像下载:北京外国语大学镜像使用ventoy做启动盘:(1)ventoy下载:github下载地址(2)解压运行下载好的ventoy,设备选择准备好的U盘(会清空),然后选择安装即可。......
  • cnblog美化 2022/11/07
    yzh2022/11/7之前的cnblog美化今天开始换美化,所以保存一下之前的美化美化效果如下这里由于不明原因,我的图片加载不出来就用了别人的图了设置界面如下页面订制css代......
  • [2022.11.7]线程
    线程就是独立的执行路径;在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;main()称之为主线程,为系统的入口,用于执行整个程序;在一个进程中,如果开辟了多......
  • 抓消防安全,保高质量发展 北京启动2022年消防宣传月活动!​
    北京市政府组织召开全市2022年消防宣传月活动启动会议,正式启动本年度消防宣传月活动,今年的活动主题为“抓消防安全,保高质量发展”。  1992年11月9日,我国首个全......
  • P2280 [HNOI2003]激光炸弹
    前缀和对于二维前缀和,令\(b[n][m]\)是\(\sum_{i=1}^n\sum_{j=1}^ma_{ij}\)的值,那么因为容斥原理,有\[b[i][j]=b[i-1][j]+b[i][j-1]-b[i-1][j-1]+a[i][j].\]要是......
  • CSP-S2022 游记
    成绩还没有出来,105分有点慌,只希望能够过线。DAY1不敢相信,这次CSP-S在本校(本人在九中光华集训)!!!下午考试,上午就直接在寝室里面睡大觉,睡到了早上10点左右,感觉精力很充沛。上......
  • 【2022-11-07】 luffy项目实战(三)
    一、luffy前台配置1.1gloabl.css/*声明全局样式和项目的初始化样式*/body,h1,h2,h3,h4,h5,h6,p,table,tr,td,ul,li,a,form,input,select,option,......
  • 【流水】2022.11.07
    今天有事考试给孩子整自闭了我切不动黄题.png难度黑-黄-蓝-红属实蚌⑨为什么数据锅了呜呜呜以及dottle是粉兔他爹.png补信息学考讲的真是骗分技巧全都是骗......