codeforces664

http://codeforces.com/contest/1395

1.

题目:有r,g,b,w四种字符,可以挑选一组r,g,b变成3个w,这个操作可以做任意次。问能否先将之操作,再将之排列,变成回文。

 

中间定律1(辅助定律):如何判断一个字符串能否重排列成回文?答:最多一种字符的数量是奇数。

辅助定律2:一次题目中的操作,意味着什么样的变化。答:意味着rgbw四种字符的数量的奇偶性都取反。边界条件:在rgb三种不为0的情况下。

solution:

不能操作的话直接判断奇字符<=1种。

能操作的话,操作前后均判断一次。

  

def gns():
    return list(map(int,input().split()))
 
t=int(input())
 
for i in range(t):
    ns=gns()
    odd=0
    evn=0
    for c in ns:
        if c%2==0:
            evn+=1
        else:
            odd+=1
    if ns[0]*ns[1]*ns[2]==0:
        if odd<=1:
            print('YES')
            continue
        else:
            print('NO')
            continue
    if evn==2:
        print('NO')
    else:
        print('YES')
View Code

 

2.

题目:寻找一种象棋“车”的遍历整个棋盘的方式(给定一个初始位置)。有点开放性的,走法随意,合理就行。

 

我的方法,两步走到(1,1)点,然后开始交错走法。

 

###嘿嘿嘿,看到bug了,太明显了,脑子🧠这个东西就是一坨肉,受限于物理性质。

def gns():
    return list(map(int,input().split()))
 
n,m,x,y=gns()
print(x,y)
print(1,y)
print(1,1)
 
for i in range(1,n+1):
    r = range(1,m+1)
    if i%2==0:
        r=reversed(r)
    for j in r:
        if (i,j)==(1,y) or (i,j)==(x,y) or (i,j)==(1,1):
            continue
        print(i,j)
View Code

3.

有n长的数组a,m长的数组b,对每个a[i]任意找一个b[j](多个ai可以找同一个bj,就是有放回的(无限的)拿)做一个“与”操作得到一个c[i],所以c长也是n。c的所以元素合做一次“或”操作得到一个x。每个a[i]如何选择b使x最大。

 

又是想的时候想半天,答案说出来30秒的问题。要意识到,大部分问题都是这种问题。

规律查找方式:“与或”操作有什么样的性质?一群数字的或操作有何性质。

辅助定律1:要最大化x,就要从他的高位bit到低位bit逐个贪心的去最大化。

观察2:每个a可以任意选b,是个很自由的条件,几乎无限制。

方法3:

方式指导:此方法要求思维跳出一小步,不要想某个a怎么选定一个b,或者最终的a选了怎样的b,虽然这两种方法在某些题目中能指向答案,但是这个题目不是。

方法内容:将这个选择看作一系列过程,而非某一次计算判断。每个a本来可以选任意的b,那么就让每个a[i]初始化是有所有b组成的一个set,然后根据贪心去做减法。当能通过目前选择去最大化目前的bit时,就去做(贪心),当不能时,其set不变,不变意味着不做减法意味着后面还有最大化x的可能性意味着最优。按照这个方式,题目中的“与或”操作,只要是bit间独立的,可以换成其他任何操作。

这样我们就把正解从题目给的顽石混沌中雕凿了出来。

关键词:过程化。

 

def gns():
    return list(map(int,input().split()))
 
n,m=gns()
ns=gns()
ms=gns()
ans=[set(ms)for i in range(n)]
 
def gt(x,i):
    return (x>>i)&1==1
 
def check(loc,i):
    rt=set()
    for x in ans[loc]:
        if  not gt(x,i):
            rt.add(x)
    return rt
 
def check_bit(i):
    for loc in range(n):
        if not gt(ns[loc],i):
            continue
        rt=check(loc,i)
        if len(rt)==0:
            return False
    return True
 
 
 
for i in reversed(range(11)):
    if not check_bit(i):
        continue
    for loc in range(n):
        if not gt(ns[loc],i):
            continue
        ans[loc]=check(loc,i)
 
# print(ans)
 
for c in ans[0]:
    x=ns[0]&c
    break
for i in range(1,n):
    for c in ans[i]:
        c=c&ns[i]
        x=x|c
        break
 
print(x)
View Code

4.

给定d,m,有一坨数a=[...],将之排列成一列,然后按照下面规则算得分:从第一个开始,如果a[i]<m,则取该得分a[i]并跳转到下一个a[i+1],如果a[i]>=m,则取该得分并跳转到a[i+d+1](就是贪的太多了,给惩罚跳过下面d个),如何排列才能最大化。

 

建议1:尽力去挖掘题目信息,比如a会重排列,所以把a看成一个有重复的set,就不要引入任何预先的排列。比如这种跳转无任何先后可言,所以可以割裂地看每次跳转操作。尽力地去将条件解耦。

观察2:大跳转导致空过几个,那么如果有大跳转,则将那个导致跳转地a[i]安排地越大越好,贪心。再思考一下那么被跳转地下脚料从哪里来(最终答案是,不用选废的,那些好的选完了,剩下的就是废的)。

观察3:思考答案的样子:有的跳下一个,有的跳过d个,这么最大化。那么我们可以安排所以的跳下一个在最左边,然后大跳在后面。

思维跳跃4:遍历所有小跳大跳的分配情况(实则遍历小跳大跳分界线),在O(1)时间给出大跳小跳分别创造的价值。O(1)实现方式,将a用阈值m分成两段,a1,a2,都做从大到小排序,算出前缀和,供大跳小跳贪心使用。

 

def gns():
    return list(map(int,input().split()))
 
n,d,m=gns()
d+=1
ns=gns()
a=[x for x in ns if x<=m]
b=[x for x in ns if x>m]
a.sort(reverse=True)
b.sort(reverse=True)
 
def sm(x):
    for i in range(1,len(x)):
        x[i]+=x[i-1]
sm(a)
sm(b)
 
ans=0
for i in range(len(a)+1):
    j=n-i
    j=j//d+(j%d!=0)
    if j>len(b):
        continue
 
    s=(a[i-1] if i>0 else 0)+(b[j-1] if j>0 else 0)
    ans=max(ans,s)
print(ans)
View Code

5.

给一个有向图,给一个在图上的行走规律:如果当前点出度为x,那么看说明书上出度x的点永远应该走第几大的边,这么走下去。所以说明书的样子是,对每个出度x,都有一个y<=x,指导他去挑选第几大的边去走。如何制作说明书,能让每个点出发都能回到此原位置。

 

观察来源有两个:从题面上直接看到(比较简单),从手动做样例中得到(输入一点耐心,得到成倍快乐)。

简化0:将边排序这个操作,可以提前完成,然后给定点与点之间的对应关系,比如a点是b点出度3排第2的,同时也是c点出度4排第1的。a点出度4,排序分别是x,m,c,e四个点,这样子。

观察1:环!如果保证每个点出发都能回到自身,那么整个图能走的部分,就是一个环。环与什么性质对等?

思维跳跃2:题面已经保证了每个点有一个出度,那么我们只要保证每个点有且只有一个入度就行了。有且只有=有且无重复。

环=每个点有一个出有一个入。

有一个出,已经满足,怎么满足有一个入?

python超时,用c++写的。

#include <iostream>
#include <vector>
#include <utility>
#include <set>
#include <map>
#include <algorithm>
using  namespace std;
 
typedef pair<int, int> pr;
int ans=0;
vector<set<pr>> fm;
int n,m,k;
set<pr> self_con;
map<pr,set<pr>> con;
vector<vector<int>> out;
vector<vector<pr>> ns;
vector<pr> cur;
 
 
void dfs(int d){
    if(d==k+1){
        ans+=1;
        return;
    }
    for(int i=0;i<d;i++){
        if(self_con.find(make_pair(i,d-1))!=self_con.end())
            continue;
        int rs=1;
        pr mkp=make_pair(i,d-1);
        for(auto it=cur.begin();it!=cur.end();it++){
            if(con[(*it)].find(mkp)!=con[(*it)].end()){
                rs=0;
                break;
            }
        }
        if(rs==1){
            cur.push_back(mkp);
            dfs(d+1);
            cur.pop_back();
        }
    }
}
 
int main() {
    std::ios::sync_with_stdio(false);
    cin>>n>>m>>k;
    fm.resize(n);
    ns.resize(n);
    for(int i=0;i<m;i++){
        int f,t,w;
        cin>>f>>t>>w;
        f--;t--;
        ns[f].push_back(make_pair(w,t));
    }
    for(int i=0;i<n;i++){
        sort(ns[i].begin(),ns[i].end());
        int ll=ns[i].size();
        for(int j=0;j<ll;j++){
            int t=ns[i][j].second;
            pr mkp=make_pair(j,ll-1);
            if(fm[t].find(mkp)!=fm[t].end())
                self_con.insert(mkp);
            else
                fm[t].insert(mkp);
        }
    }
    for(int i=0;i<n;i++){
        for(auto j=fm[i].begin();j!=fm[i].end();j++){
            auto k=j;
            k++;
            for(;k!=fm[i].end();k++){
                con[*j].insert(*k);
                con[*k].insert(*j);
            }
        }
    }
 
    dfs(1);
    cout<<ans<<endl;
 
 
    return 0;
}
View Code

6.

一个只有ab组成的字符串,一次操作可以如下选1个:

1.删其中一个a

2.删其中一个b

3.删其中一个‘ab’

4.删其中一个‘ba’

5.在结尾加“ab”

6.在结尾加“ba”

然后定义“x等价”是两个字符串各字符数量相同(就是两字符串“成分”相同)。

使一个串a等价与串b的最少操作次数是两者的“距离”。

给一堆串,输出一个与所有串的距离最大值最小化的串。

 

观察“操作”的本质。

此操作可以转化成平面图问题,然后转换成那个找到最近补给点的问题。

对于一个平面棋盘,两个坐标代表两种字符数量,6个操作代表6种行走方向,距离代表按照这6种行走方式下两个点的距离。

题目转化为,平面棋盘很多点,给定很多点,找到一个位置,使距离这些点的最大距离最小化。

再怎么做,二分呗!

距离x能满足要求吗,能的话再找大一点的,不能就再找小一点的。

那么这个check能不能的函数,时间复杂度多少呢。

利用所有点的距离x的覆盖范围有无交集来check。

用到很多题目特性,本题的距离x覆盖范围是一个6边形的凸包,凸包与凸包的交集还是单个凸包。求交集可以用顺时针转移的方法来做,要check一个点在当前线的行进方向的左侧还是右侧,还有很多其他边界条件给我写吐了。

在写这些的时候,突然想到了一个新方法,既然是很多平行的边,那么就按照6条边分别求交集,这么着不就确定范围了吗!!!!!不同维度的信息可以解耦。

 

posted @ 2020-08-14 10:21  Cloud.9  阅读(127)  评论(0编辑  收藏  举报