2022杭电多校1

1002 Dragon slayer

题目大意:

给出一张的网格地图,给出起点和终点(均为方格的正中心),其中地图中有面墙阻隔了道路,每面墙都在网格线上并保证墙横平竖直,以线段端点的形式给,

问从起点到终点最少要破坏多少面墙

分析:

发现数据都是<=15 所以直接枚举破坏墙的方案

对每一种二进制方案bfs判断是否可以到达终点

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")

#define endl '\n'

#define pii pair<int,int>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

const int maxn=1005;

int n,m,k;

int vis[maxn];

int st[maxn][maxn],pos[maxn][maxn];

int dx[5]={0,1,0,-1};
int dy[5]={1,0,-1,0};

struct node {
    int a,b,c,d;
}que[maxn];


int main(){

    IOS

    int T;
    cin>>T;
    while(T--){
        cin>>n>>m>>k;
        int ans=k;
        for(int i=0;i<=100;i++){
            for(int j=0;j<=100;j++){
                pos[i][j]=0;
            }
        }
        int cnt=0;
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                pos[i][j]=++cnt;
            }
        }
        int u,v,s,t;
        cin>>u>>v>>s>>t;
        for(int j=0;j<k;j++){
            int x1,y1,x2,y2;
            cin>>x1>>y1>>x2>>y2;
            que[j]={x1,y1,x2,y2};
        }
        for(int ji=0;ji<(1<<k);ji++){
            for(int i=0;i<=n*m+5;i++)vis[i]=0;
            int res=k;
            for(int i=0;i<k;i++){
                if(ji>>i&1){
                    int x1=que[i].a,y1=que[i].b,x2=que[i].c,y2=que[i].d;
                    if(x1>x2)swap(x1,x2);
                    if(y1>y2)swap(y1,y2);
                    if(x1==x2){
                        for(int j=y1;j<y2;j++){
                            st[pos[x1-1][j]][pos[x2][j]]=1;
                            st[pos[x2][j]][pos[x1-1][j]]=1;
                        }
                    }else {
                        for(int j=x1;j<x2;j++){
                            st[pos[j][y1-1]][pos[j][y1]]=1;
                            st[pos[j][y1]][pos[j][y1-1]]=1;
                        }
                    }
                    res--;
                }
            }
            queue<pii>q;
            q.push({u,v});
            while(q.size()){
                pii top=q.front();
                q.pop();
                vis[pos[top.first][top.second]]=1;
                for(int j=0;j<4;j++){
                    int x=top.first+dx[j],y=top.second+dy[j];
                    if(x<0||x>=n||y<0||y>=m)continue;
                    if(vis[pos[x][y]])continue;
                    if(st[pos[top.first][top.second]][pos[x][y]])continue;
                    q.push({x,y});
					vis[pos[x][y]]=1;
                }
            }
            for(int i=0;i<k;i++){
                if(ji>>i&1){
                    int x1=que[i].a,y1=que[i].b,x2=que[i].c,y2=que[i].d;
                    if(x1>x2)swap(x1,x2);
                    if(y1>y2)swap(y1,y2);
                    if(x1==x2){
                        for(int j=y1;j<y2;j++){
                            st[pos[x1-1][j]][pos[x2][j]]=0;
                            st[pos[x2][j]][pos[x1-1][j]]=0;
                        }
                    }else {
                        for(int j=x1;j<x2;j++){
                            st[pos[j][y1-1]][pos[j][y1]]=0;
                            st[pos[j][y1]][pos[j][y1-1]]=0;
                        }
                    }
                }
            }
            if(vis[pos[s][t]])ans=min(ans,res);
        }
        cout<<ans<<endl;
    }
}

1011 Random


签到题没啥说的

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

#define endl '\n'

#define pii pair<int,int>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

const int maxn=1e6+5;
const int mod=1e9+7;

ll ksm(ll a,ll b){
    ll ans=1,base=a%mod;
    while(b){
        if(b&1)
            ans=ans*base%mod;
        base=base*base%mod;
        b>>=1;
    }
    return ans;
}

ll inv(ll x){
    return ksm(x,mod-2)%mod;
}

int main(){

    IOS
    int t;
    cin>>t;
    while(t--){
        int n,m;
        cin>>n>>m;
        cout<<(n-m)*inv(2)%mod<<endl;
    }
}

L Alice And Bob

题意:

有n个数,每次Alice可以把这些数分成两个集合,Bob可以删除掉任意一个集合里的所有元素,然后另一个集合内所有元素-1,如果集合中出现了0,那么

Alice获胜,如果集合内没有元素了Bob获胜。

分析:

这个题还是挺有难度的

首先想Ailce必胜 如果此时有至少两个1 我们把这两个1分别放一堆 这样是必胜

继续想后续状态 也就是有四个2 八个3 ....这样都是必胜

但是除了这样就没有别的必胜的状态了嘛? 肯定不是

比如 2 2 2 3 3

第一堆 2 2

第二堆2 3 3

Bob只有将第一堆删去 但是这样第二堆变成1 2 2 了

我们再分

第一堆 1

第二堆 2 2

同样也是必胜状态

怎么总结这个规律呢?

两个相同的数 等价于一个减一的数

从大到小依次处理就好

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

#define endl '\n'

#define pii pair<int,int>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

const int maxn=1e6+5;
const int mod=1e9+7;

int n,a[maxn];
int main(){

    IOS
    int t;
    cin>>t;
    while(t--){
        cin>>n;
        int flg=0,now=1;
        for(int i=0;i<=n;i++)cin>>a[i];
        for(int i=n;i>=1;i--)a[i-1]+=a[i]/2;

        if(a[0])cout<<"Alice"<<endl;
        else cout<<"Bob"<<endl;
    }
}

1003 Backpack

题目大意:

01背包求恰好装满时的价值最大异或和

分析:

让我想到了这个题目

https://www.acwing.com/problem/content/1456/

这个题目就直接转移就好

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7, N = 1 << 13;
signed main()
{
    cin.tie(0)->sync_with_stdio(0);
    int cnt = 0;
    vector<int> prime(N + 1);
    vector<bool> st(N + 1);
    auto sieve = [&](int n)
    {
        st[1] = 1;
        for (int i = 2; i <= n; i++)
        {
            if (!st[i])
                prime[cnt++] = i;
            for (int j = 0; i * prime[j] <= n; j++)
            {
                int t = i * prime[j];
                st[t] = 1;
                if (i % prime[j] == 0)
                    break;
            }
        }
    };
    sieve(N);
    int n;
    cin >> n;
    vector<int> a(n + 1);
    vector<vector<int>> dp(2, vector<int>(N + 1));
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= N; j++)
        {
            dp[i & 1][j] = dp[(i - 1) & 1][j];
            if ((j ^ a[i]) <= N)
            {
                dp[i & 1][j] = (dp[i & 1][j] + dp[(i - 1) & 1][j ^ a[i]]) % mod;
            }
        }
    }
    int res = 0;
 
    for (int i = 1; i <= N; i++)
    {
        if (!st[i])
            res = (res + dp[n & 1][i]) % mod;
    }
 
    cout << res << endl;
}

回到正题

正常写暴力dp的话 dp[i][j][k]表示前 i 个 异或值为 j 此时背包容量为 k 是否存在

转移: dp[i][j][k]|=dp[i-1][j^w][k-a[i]] 这样n三方的复杂度肯定不行

考虑优化 因为背包容量最大为1024 所以考虑用到bitset优化

将第三维背包容量往后调到dp值上

dp[i][j] 表示前 i 个 异或值为 j 背包容量的集合

集合?比如011101 就是异或和为j 的可能背包容量为 0 2 3 4 [左移所少位背包容量就是多少]

初始背包容量为0 二进制下也就是第一位为1

背包容量加上v就是向左移动v位即可

最后从大到小枚举异或和 在满背包的情况下 即第m位为1

bitset真的很好用 !!!

当转移只有 i 和 i-1 的时候 用滚动数组优化

一定是用滚动数组!!! 不要轻易的将第一维消掉!!!!

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")

#define endl '\n'

#define pii pair<int,int>
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

const int maxn=1050;

bitset<maxn>f[2][maxn];

int main(){

    IOS

    int t;
    cin>>t;

    while(t--){
        int n,m;
        cin>>n>>m;
        for(int i=0;i<1024;i++)f[0][i].reset();
        f[0][0].set(0);
        for(int i=1;i<=n;i++){
            int v,w;
            cin>>v>>w;
            for(int j=0;j<1024;j++){
                f[i&1][j]=f[(i&1)^1][j]|(f[(i&1)^1][j^w]<<v);
            }
        }
        int ans=-1;
        for(int i=1023;i>=0;i--){
            if(f[n&1][i][m]){
                ans=i;
                break;
            }
        }
        cout<<ans<<endl;
    }
}

D Ball

题意:

有n个点在棋盘上,请找到一个三元组使得三个点之间距离的中位数是个素数。其中距离指的是哈密顿距离。

dist = (abs(x1 - x2) + abs(y1 - y2))

分析:

数据最多满足n方的复杂度 所以先枚举两两点的距离 再从小到大排序

开始枚举已经排好序的边

对于一条边<a,b> 使他为中位数 要找出前面出现的点到a/b 后面还没出现的点到b/a 的个数

因为前面出现的点是集合的形式 考虑用bitset

p[i]表示已经出现到 i 的点的集合

a和b不能同时出现 已经更新了的点 [因为要保证当前边一定是中位数]

p[i] xor p[j] 即可

#include <iostream>
#include <cstring>
#include <algorithm>
#include <bitset>

using namespace std;
const int N = 2010;
int n, m, a[N], b[N];
bitset<N> p[N];
struct Edge
{
    int a, b, w;
    bool operator < (const Edge &t) const
    {
        return w < t.w;
    }
}edges[N * N];
bool st[N * N];
int primes[N * N], cc;
int cnt, ans;

void init()
{ // 线性筛法
    for(int i = 2 ; i < 200010 ; i ++ )
    {
        if(!st[i]) primes[cnt ++ ] = i;
        for(int j = 0 ; primes[j] * i < 200010 ; j ++ )
        {
            st[primes[j] * i] = true;
            if(i % primes[j] == 0) break;
        }
    }
}

int calc(int i, int j)
{
    return abs(a[i] - a[j]) + abs(b[i] - b[j]);
}

void solve()
{
    cin >> n >> m;
    cnt = 0, ans = 0;
    for(int i = 0 ; i <= n ; i ++ ) p[i].reset();
    for(int i = 1 ; i <= n ; i ++ ) cin >> a[i] >> b[i];
    for(int i = 1 ; i <= n ; i ++ )
        for(int j = i + 1; j <= n ; j ++ ) 
            edges[++ cnt] = {i, j, calc(i, j)};
    sort(edges + 1, edges + cnt + 1); // 按距离排序,先枚举的边一定比当前的小,做好标记即可。
    for(int i = 1 ; i <= cnt ; i ++ )
    {
        int a = edges[i].a, b = edges[i].b, w = edges[i].w;
        if(!st[w]) ans += (p[a] ^ p[b]).count(); // p[a]表示距离比w小的点的数量 , st表示他是不是素数
        p[a][b] = 1, p[b][a] = 1;
    }
    cout << ans << endl;
}

int main()
{
    init();
    int T = 1;
    cin >> T;
    while(T -- ) solve();
    return 0;
}

H Path

题意:

最短路问题,给定一个带权有向图,其中包含特殊边和普通边,特殊边的特殊之处在于我们经过特殊边之后对于不相邻的点我们可以

免费传送过去,不花费时间,对于相邻的边花费时间w[i] 变成w[i] - k,但是不是永久-k只是在经过这条边之后,最后请问从起点到

所有点的最短距离。

分析:

很明显 最短路数组dis[t][0/1]表示上一步是普通边还是特殊边到t的最短距离

关键点在于 特殊边是能够到达任意一点的 不可能每次都枚举

利用最短路的性质 我们枚举只用枚举还没到达过的点 可以用set维护

转移

如果是普通边 直接转移就好

如果是特殊边 转移集合里非相临边 相临边按照普通边-k转移就好

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#include <set>
#include <queue>

using namespace std;
#define int long long
const int N = 5e5 + 10, M =  N * 2, INF = 1e18;
int n, m, s, k;
int h[N], ne[M], e[M], w[M], p[M], idx;
struct Node
{
    int a, d, p;
    bool operator < (const Node &t) const
    {
        return d > t.d;
    }
};
bool st[N][2];
int dist[N][2];
int gg[N]; // 标记

void add(int a, int b, int c, int d)  // 添加一条边a->b,边权为c
{
    e[idx] = b, w[idx] = c, p[idx] = d, ne[idx] = h[a], h[a] = idx ++ ;
}

void bfs()
{
    set<int> S;
    for(int i = 1 ; i <= n ; i ++ )
    {
        if(i != s) S.insert(i);
        st[i][0] = st[i][1] = false;
        dist[i][0] = dist[i][1] = INF;
    }
    dist[s][0] = 0;
    priority_queue<Node> q;
    q.push({s, 0, 0});
    int cnt = 0;
    while(q.size())
    {
        auto t = q.top();
        int ty = t.p, ver = t.a;
        q.pop();
        cnt ++ ;
        if(!ty) S.erase(ver);
        else
        {
            for(int i = h[ver] ; ~i ; i = ne[i])
            {
                int j = e[i];
                gg[j] = cnt; // 这个点是邻边
            }
            vector<int> temp;
            for(auto it : S)
            {
                if(gg[it] != cnt) // 没有被遍历的非相邻的点
                {
                    temp.push_back(it);
                    dist[it][0] = dist[ver][ty];
                    q.push({it, dist[it][0], 0});//相当于添加源点
                }
            }
            for(auto it : temp) S.erase(it); // 被更新的点被遍历过 所以直接删除
        }
        
        int y = 0;
        if(ty) y -= k;

        if(st[ver][ty]) continue; 
        st[ver][ty] = true;
        for(int i = h[ver] ; ~i ; i = ne[i])
        {
            int j = e[i];
            if(dist[j][p[i]] > dist[ver][ty] + w[i] + y)
            {
                dist[j][p[i]] = dist[ver][ty] + w[i] + y;
                q.push({j, dist[j][p[i]], p[i]});
            }
        }
    }
}   

void solve()
{
    cin >> n >> m >> s >> k;
    for(int i = 0 ; i <= n ; i ++ ) h[i] = -1, gg[i] = 0;
    idx = 0;
    for(int i = 1 ; i <= m ; i ++ )
    {
        int a, b, c, d;
        cin >> a >> b >> c >> d;
        add(a, b, c, d);
    }
    
    bfs();
    for(int i = 1 ; i <= n ; i ++ ) 
        if(min(dist[i][0], dist[i][1]) == INF) cout << -1 << " ";
        else cout << min(dist[i][0], dist[i][1]) << " ";
    cout << endl;
}

signed main()
{
    ios::sync_with_stdio(0),cin.tie(0);
    int T = 1;
    cin >> T;
    while(T -- ) solve();
    return 0;
}
posted @ 2022-09-28 11:24  wzx_believer  阅读(61)  评论(0编辑  收藏  举报