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')
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)
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)
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)
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; }
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条边分别求交集,这么着不就确定范围了吗!!!!!不同维度的信息可以解耦。