The 2023 ICPC Asia Macau Regional Contest (The 2nd Universal Cup. Stage 15: Macau)

Dashboard - The 2023 ICPC Asia Macau Regional Contest (The 2nd Universal Cup. Stage 15: Macau) - Codeforces

0题场..补了五题,第六题是一个树形dp,不是很懂..

A (codeforces.com)

思路:挺思维的转化.

把-1转化为1,当做1来使用.
仔细想,-1的作用其实是使得总贡献-1或不变.
据此,如果把-1改成了1,那么对于其对应的行列的x,应该x++.
此时:如果不选择当前的1,即选择了原来的-1.(总贡献为-1,因为当前贡献0,然后提前加了1)<-->如果选择了当前的1,即没有选择原来的-1,即总贡献0.
当把-1全部转化为1之后,并且对应行列需要的值也更改之后,剩下的就是贪心了.
要注意的是,原本是-1的格子,最后输出的状态要和选择相反.
int n,x;
bool maze[4005][4005];
int row[4005],col[4005];
bool ans[4005][4005];
把-1转化为1,当做1来使用.
仔细想,-1的作用其实是使得总贡献-1或不变.
据此,如果把-1改成了1,那么对于其对应的行列的x,应该x++.
此时:如果不选择当前的1,即选择了原来的-1.(总贡献为-1,因为当前贡献0,然后提前加了1)<-->如果选择了当前的1,即没有选择原来的-1,即总贡献0.
当把-1全部转化为1之后,并且对应行列需要的值也更改之后,剩下的就是贪心了.
要注意的是,原本是-1的格子,最后输出的状态要和选择相反.
void solve(){       //补A--思维题->究其根本  nb,好
    cin>>n;\
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            char c; cin>>c;
            c=='+'?maze[i][j]=1:(maze[i][j]=0,row[i]++,col[j]++);
        }
    }
    vector<pair<int,int>> vct;
    for(int i=1;i<=n;i++)cin>>x,row[i]+=x; //行
    for(int j=1;j<=n;j++) cin>>x,col[j]+=x,vct.emplace_back(col[j],j); //列
    sort(vct.begin(),vct.end(),greater<>());
    //贪心考虑每一行,优先填需要多的列.
    for(int i=1;i<=n;i++){
        int idx=0;
        while(row[i]&&idx<vct.size()){
            if(vct[idx].first<1) break;
            vct[idx].first--,row[i]--;
            ans[i][vct[idx].second]=1;
            idx++;
        }
        if(row[i]){ cout<<"No"; return; } 如果行不能满足--No
        sort(vct.begin(),vct.end(),greater<>());
    }
    if(vct[0].first!=0){ cout<<"No"; return; }  如果行满足了,列仍然未满足--No
    cout<<"Yes"<<endl;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(maze[i][j]) cout<<ans[i][j];
            else ans[i][j]?cout<<"0":cout<<"1";
        }
        cout<<endl;
    }
}

Problem - D - Codeforces

思路:

虽说是数合法子图的方案树,正常想到肯定认为是很多的,并且题目还说ans%998244353.
有个很关键的条件:图中最大的度数不大于3.但是不知道这是干什么的,以为是dfs的时候降低复杂度?
通过画图可以发现(我没有发现..),看了一眼题解这样说的,再简单画一下图,的的确确是这样的:
合法的子图,最多包含四个节点. 因为此时4个节点都已经是度为3了. 样例也有这个,但是居然没有发现.
题目保证的关键条件:图中最大度数不大于3,那么节点为5,6..的图都是不可能满足只要红蓝边仍然保持联通.
所以可能合法的子图,只会包含<=4个节点. 题目中的ans%998244353纯诈骗..can u believe it?
观察一下节点数为3的合法图,一定至少有一条双色边.那么这样判三个点的答案时,只需要先找一条双色边,然后判出边是否颜色不一样,并且连向同一个点即可.
但是怎么找出全部大小为4的联通块?...经典问题:枚举两个点,然后在这两个点的出度之中匹配即可得到大小为4的联通块.
tip:以后写题还是得多画画 "合法的图" 和 "不合法的图".
实现写的太炸了..TLE34..把遍历所有边改成遍历红蓝边分开遍历,然后把多余的存的东西删掉了,刚刚好可以卡到980ms,虽然说给了1.5s
const int N=100005;
int n,m;
vector<int> red[N],blue[N]; //红蓝拆开
vector<pair<int,int>> edgeR;  //不必用set存
map<pair<int,int>,bool> markR,markB;
虽说是数合法子图的方案树,正常想到肯定认为是很多的,并且题目还说ans%998244353.
有个很关键的条件:图中最大的度数不大于3.但是不知道这是干什么的,以为是dfs的时候降低复杂度?
通过画图可以发现(我没有发现..),看了一眼题解这样说的,再简单画一下图,的的确确是这样的:
合法的子图,最多包含四个节点. 因为此时4个节点都已经是度为3了. 样例也有这个,但是居然没有发现.
题目保证的关键条件:图中最大度数不大于3,那么节点为5,6..的图都是不可能满足只要红蓝边仍然保持联通.
所以可能合法的子图,只会包含<=4个节点. 题目中的ans%998244353纯诈骗..can u believe it?
观察一下节点数为3的合法图,一定至少有一条双色边.那么这样判三个点的答案时,只需要先找一条双色边,然后判出边是否颜色不一样,并且连向同一个点即可.
但是怎么找出全部大小为4的联通块?...经典问题:枚举两个点,然后在这两个点的出度之中匹配即可得到大小为4的联通块.
tip:以后写题还是得多画画 "合法的图" 和 "不合法的图".
//实现写的太炸了..TLE34..把遍历所有边改成遍历红蓝边分开遍历,然后把多余的存的东西删掉了,刚刚好可以卡到980ms,虽然说给了1.5s
void solve(){          //补D    好题..
    cin>>n>>m;
    int ans=n;
    for(int i=1;i<=m;i++){
        int u,v,c; cin>>u>>v>>c;
        if(u>v) swap(u,v);
        if(c) red[u].emplace_back(v),red[v].emplace_back(u),edgeR.emplace_back(u,v),markR[{u,v}]=1;
        else blue[u].emplace_back(v),blue[v].emplace_back(u),markB[{u,v}]=1;
    }
    for(auto [u,v]:edgeR){
        if(markR[{u,v}]&&markB[{u,v}]){ 找到双色边
            //节点数为2
            ans++;
            //节点数为3,用红蓝边来找
            bool cc=false;
            for(auto u0:red[u]){
                for(auto v0:blue[v]){
                    if(u0==v0){
                        cc=true;
                        break;
                    }
                }
                if(cc) break;
            }
            if(!cc){
                for(auto v0:red[v]){
                    for(auto u0:blue[u]){
                        if(u0==v0){
                            cc=true;
                            break;
                        }
                    }
                }
            }
            if(cc) ans++;
        }
    }
    map<array<int,4>,bool> mark;
    //节点数为4--改成用red的边和blue的边来找之后能ac,跑了1100ms,还不够快.别人的都是跑400ms左右,如果这题给了1ms就T了.
    for(int i=1;i<=n;i++){
        for(auto v:red[i]){
            //i&v 为中间两个点
            for(auto v1:red[v]){   //两个点往外延伸
                if(v1==i) continue;
                for(auto v2:red[i]){
                    if(v2==v) continue;
                    array<int,4> temp={i,v,v1,v2};
                    sort(temp.begin(),temp.end());
                    //标记red中所有的四元组
                    mark[temp]=1;
                }
            }
        }
    }
    set<array<int,4>> st;  //去重
    for(int i=1;i<=n;i++){
        for(auto v:blue[i]){
            //i&v 为中间两个点
            for(auto v1:blue[v]){
                if(v1==i) continue;
                for(auto v2:blue[i]){    //两个点往外延伸
                    if(v2==v) continue;
                    array<int,4> temp={i,v,v1,v2};
                    sort(temp.begin(),temp.end());
                    //如果red中也有这个temp,那么存入set.
                    if(mark[temp]) st.emplace(temp);
                }
            }
        }
    }
    cout<<ans+st.size();
}

Problem - E - Codeforces

思路:突然发现自己对拓扑排序的理解很不对劲..

思考一个问题:为什么会出现逆序?
在字典序小的拓扑排序中,出现逆序的原因,是因为不得不先选小的,才能选到那个大的.即可能存在边大->小,但可能是间接的
同理在字典序大的拓扑排序中,出现逆序的原因,是因为不得不先选大的,才能选到小的.即可能存在边小->大,但可能是间接的
如果全部逆序对都建边,那么最差有o(n^2)条边.
key:当连x->y->z时,就满足了x对z的限制,而不必要连x->z. 传递性.
key:只需要给每个逆序对中离它最近的那个点建边即可.即限制它的最近的那个.ST表+二分 / 单调栈
即对于小字典序,每个数字找第一个比它大的建边;对于字典序大的,每个数字找第一个比它小的建边
这样建边最多只会建2*n条,建完之后自己跑一遍拓扑.如果存在环 或 跟输入不一样--No
int n;
vector<int> vct[100005];
在字典序小的拓扑排序中,出现逆序的原因,是因为不得不先选小的,才能选到那个大的.即可能存在边大->小,但可能是间接的
同理在字典序大的拓扑排序中,出现逆序的原因,是因为不得不先选大的,才能选到小的.即可能存在边小->大,但可能是间接的
如果全部逆序对都建边,那么最差有o(n^2)条边.
key:当连x->y->z时,就满足了x对z的限制,而不必要连x->z. 传递性.
key:只需要给每个逆序对中离它最近的那个点建边即可.即限制它的最近的那个.ST表+二分 / 单调栈
即对于小字典序,每个数字找第一个比它大的建边;对于字典序大的,每个数字找第一个比它小的建边
这样建边最多只会建2*n条,建完之后自己跑一遍拓扑.如果存在环 或 跟输入不一样--No
//警钟长鸣:写了半天发现自己不会拓扑排序..拓扑排序是先脱入度为0的点,而不是出度为0的点..
//警钟长鸣:写了半天发现自己不会拓扑排序..拓扑排序是先脱入度为0的点,而不是出度为0的点..
void solve(){        //补E           单调栈写的,比ST表+二分快一半.
    cin>>n;
    vector<int> smal(n+10,0),big(n+10,0);
    stack<int> stk;
    vector<int> du(n+10,0);
    int edge=0;
    for(int i=1;i<=n;i++) {
        cin>>smal[i];
        //每个数字向左找第一个比它大的
        while((int)stk.size()&&stk.top()<smal[i]) stk.pop();
        if((int)stk.size()&&stk.top()>smal[i]) vct[stk.top()].emplace_back(smal[i]),du[smal[i]]++,edge++;
        stk.emplace(smal[i]);
    }
    while((int)stk.size()) stk.pop();
    for(int i=1;i<=n;i++) {
        cin>>big[i];
        //每个数字向左找第一个比它小的
        while((int)stk.size()&&stk.top()>big[i]) stk.pop();
        if((int)stk.size()&&stk.top()<big[i]) vct[stk.top()].emplace_back(big[i]),du[big[i]]++,edge++;
        stk.emplace(big[i]);
    }
    priority_queue<int> pqBig;
    priority_queue<int,vector<int>,greater<int>> pqSmal;
    for(int i=1;i<=n;i++){
        if(du[i]==0){
            pqBig.emplace(i);
            pqSmal.emplace(i);
        }
    }
    auto du0=du; //拓扑两次,备份一个
    vector<int> ansSmal(n+10,0),ansBig(n+10,0);
    int idxSmal=0;
    while((int)pqSmal.size()){
        int top=pqSmal.top();
        pqSmal.pop();
        ansSmal[++idxSmal]=top;
        for(auto v:vct[top]){
            du[v]--;
            if(du[v]==0) pqSmal.emplace(v);
        }
    }
    int idxBig=0;
    while((int)pqBig.size()){
        int top=pqBig.top();
        pqBig.pop();
        ansBig[++idxBig]=top;
        for(auto v:vct[top]){
            du0[v]--;
            if(du0[v]==0) pqBig.emplace(v);
        }
    }
    if((idxSmal!=n||idxBig!=n)&&edge!=0){ cout<<"No"; return; }
    if(ansSmal!=smal||ansBig!=big){ cout<<"No"; return; }
    cout<<"Yes"<<endl<<edge<<endl;
    for(int i=1;i<=n;i++){
        for(auto v:vct[i]) cout<<i<<" "<<v<<endl;
    }
}
原本以为写的没问题了,直接wa4,那就得换思路,找不是自己想法的样例了..
wa4:--这种样例按想法来说是判No的,但是这个是有答案的..这个图不能分层,怎么有答案?--完全想不明白->看正确答案输出
//拓扑序是一个一个脱落当前叶子节点,而不是一层一层脱落..根本性的错误..那就没那么简单了..感觉这个建图有点像结论,还是没想到..
补都补半天,呃
10
6 8 9 4 1 3 7 5 10 2
8 6 9 10 4 7 5 3 2 1

Problem - I - Codeforces

思路:实际上这个dp跟贪心很像很像,但是就是没发现怎么定义dp[i]的状态.

赛时一直认为是贪心,一直贪一直wa.
一种是刷新球CD一好就马上用;另一种是刷新球CD好了先等到最近的一次刷钱CD也转好.在这两种中一直选,到最后一段特判. wa2
正解是dp,定义dp[i]为,当a,b技能在i时刻同时进入cd,能够使用最多的技能次数--想不到这样定义..
这样定义是极对的,因为使用都是a a a b a.这样形式的,最后一次总是ba,然后ab同时进入cd状态
只不过是要么是b有马上用,或者b等下一个a一起使用,实质上都是b a,那么完全可以据此定义dp[i]为在第i秒时,ab同时进入cd了,可以得到的最大的使用次数
int a,b,T;
赛时一直认为是贪心,一直贪一直wa.
一种是刷新球CD一好就马上用;另一种是刷新球CD好了先等到最近的一次刷钱CD也转好.在这两种中一直选,到最后一段特判. wa2
正解是dp,定义dp[i]为,当a,b技能在i时刻同时进入cd,能够使用最多的技能次数--想不到这样定义..
这样定义是极对的,因为使用都是a a a b a.这样形式的,最后一次总是ba,然后ab同时进入cd状态
只不过是要么是b有马上用,或者b等下一个a一起使用,实质上都是b a,那么完全可以据此定义dp[i]为在第i秒时,ab同时进入cd了,可以得到的最大的使用次数
https://codeforces.com/gym/104891/problem/I
void solve(){       //补I--这题仍然定位为签到题..好题..
    cin>>a>>b>>T;
    vector<int> dp(T+10,0);
    dp[0]=2;
    int ans=2;
    for(int i=0;i<=T;i++){
        if(dp[i]){
            b冷却了马上使用
            if(i+b<=T) dp[i+b]=max(dp[i+b],dp[i]+b/a+1);
            b冷却好了,等下一个a使用完再使用b. (b+a-1)/a 为 b/a向下取整
            int nex=b/a;
            if(b%a!=0) nex++;
            if(i+nex*a<=T) dp[i+nex*a]=max(dp[i+nex*a],dp[i]+nex+1);
//            ans=max(ans,dp[i]);
            ans=max(ans,dp[i]+(T-i)/a); //细节:剩下的时间用a,因为可能等不到下一个b的刷新了
        }
    }
    cout<<ans*160<<endl;
}

Problem - J - Codeforces

思路:这题不用考虑时针是否会重置,时针拨就完了。

建一个分层图,有三种边:
①正常的第一步,i到达下一层的(i+x)%n
②拨动时钟:只有到了下一层之后,才能加1的代价,到达下一个点,而不能直接i->i+1建边
③0的代价可以到达自己的原本点

int n,destination;
vector<pair<int,int>> vct[200005];
deque<int> dq;
int dis[200005];
bool vis[200005];
void bfs01(int s){
    for(int i=0;i<2*n;i++) dis[i]=INT_MAX,vis[i]=0;
    dis[s]=0;
    dq.emplace_front(s);
    while(dq.size()){
        int from=dq.front();
        dq.pop_front();
        if(vis[from]) continue;
        vis[from]=1;
        for(auto v:vct[from]){
            int to=v.first,w=v.second;
            if(dis[to]>dis[from]+w){
                dis[to]=dis[from]+w;
                w?dq.emplace_back(to):dq.emplace_front(to);
            }
        }
    }
}
因为边权只有0/1,可以改成01bfs
https://codeforces.com/gym/104891/problem/J
void solve(){       //补J--定位为签到题..key:建图--分层图  完完全全没想到分层图,也没有留意到i->i+1建边的性质.. 好题..
    cin>>n>>destination;
    for(int i=0;i<n;i++){
        int x; cin>>x;
        分层图的思想!! 令人叹为观止的建图.
        vct[i].emplace_back(n+(i+x)%n,1);   正常的第一步,i到达下一层的(i+x)%n
        vct[n+i].emplace_back(n+(i+1)%n,1); 拨动时钟:只有到了下一层之后,才能加1的代价,到达下一个点,而不能直接i->i+1建边
        vct[n+i].emplace_back(i,0);         0的代价可以到达自己的原本点
    }
    bfs01(0);
    cout<<dis[destination];
}

这场补了三天,补了五题,感觉收获满满。加练加练。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值