• Codeforces Round #397 题解


    Problem A. Neverending competitions

    题目大意

    一个团队有多个比赛,每次去比赛都会先订机票去比赛地点,然后再订机票返回.给出(n)个含有起止地点的购票记录(不按时间顺序),判断这个团队是否返回.

    题解

    竞速题
    因为每次比赛都必定是一去一返
    所以当订票记录个数为偶数的时候一定在家中,否则一定在比赛.

    Code

    a = input()
    if a % 2 == 0:
    	print 'home'
    else:
    	print 'contest'
    

    Problem B. Code obfuscation

    题目大意

    在一个不同的词数目不超过26种且单个词长度>1的文档中,做如下替换:从前到后找出第一个长度>1的单词,并把所有这种单词替换成a,然后继续找..替换成b,继续..替换成c,继续...直到不存在这样的单词位置.然后删去所有的不可见字符构成一个字符串
    现在给定一个字符串,判断这个字符串有没有可能是由上述替换得到的.
    (len leq 500)

    题解

    这个题面很长也不要怪我啊...
    我们发现这样得到的字符串一定满足这样的一个性质:
    若字母(ch)出现,那么('a' .. (ch-1))一定都在之前就出现过了
    所以我们应用这个性质直接判断即可.

    Code

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    inline void read(int &x){
    	x=0;char ch;bool flag = false;
    	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
    }
    char s[512];
    int main(){
    	scanf("%s",s+1);
    	int n = strlen(s+1);
    	int nw = 'a' - 1;
    	for(int i=1;i<=n;++i){
    		if(s[i] == nw+1) ++ nw;
    		if(s[i] > nw) return puts("NO");
    	}
    	puts("YES");
    	return 0;
    }
    

    Problem C. Table Tennis Game 2

    题目大意

    一场比赛结束当且仅当某一方的得分恰好为(k),现在给出若干场比赛后小M和小V的各场比赛得分之和,判断是否合法.若合法,则输出可能的最多比赛的
    场数.(k,a,b leq 10^9)

    题解

    设得分分别为(sco_a,sco_b)
    我们知道合法时最多可能的比赛场数一定为(frac{sco_a}{k} + frac{sco_b}{k})
    那么现在问题就在于判定是否合法
    我们知道如果一个局面不合法,一定是有某一方有余出来的无法解释来由的得分
    例如(k = 2,sco_1 = 1,sco_2 = 1)
    双方都无法解释得分的来由
    但是如果有(k = 2,sco_1 = 3,sco_2 = 1)
    (sco_2)就可以解释的通了
    所以我们可以做如下判定
    if( ( a < k && b % k) || (b < k && a % k) ) puts("-1");

    Code

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    inline void read(int &x){
    	x=0;char ch;bool flag = false;
    	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
    }
    int main(){
    	int k,a,b;read(k);read(a);read(b);
    	if( ( a < k && b % k) || (b < k && a % k) ) puts("-1");
    	else printf("%d
    ",a/k + b/k);
    	return 0;
    }
    

    Problem D. Artsem and Saunders

    题目大意

    定义([n])表示集合({1,2,..,n})给定函数(f:[x]->[y])要求构造函数:(g:[n]->[m])(h:[m]->[n])满足(g(h(x)) = x,x in [m])(h(g(x)) = f(x),x in [n])。存在则输出,不存在输出-1.((n leq 10^5,m leq 10^6))

    题解

    其实这道题就是让我们构建一系列的映射关系
    我们发现要求(g(h(x)) = x)那么我们知道对于所有存在的(h(x))
    一定有(g(h(x)) = x),换句话说,如果我们知道了(h(x))那么我们就知道(g(h(x)))
    但是我们这样就直接确定了(g(x))有可能(并且是大多数情况下)不满足(g(h(x)) = x)
    我们想,在(i,f(i))是什么情况的时候,有前式直接确定的(g(x))满足后式
    只有当(i = f(i))的时候,我们可以令(h(m) = i,g(i) = m)
    那么当(i != f(i))呢,如果(f(f(i)) = f(i))也就是我们映射到的元素所发出的映射已经被(h(x),g(x))映射过了
    那么我们就加一条(g(i) = g(f(i)))的映射就依然满足条件了。
    如果(f(f(i)) != f(i))那就无论如何都不可能有解了。

    Code

    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    inline void read(int &x){
    	x=0;char ch;bool flag = false;
    	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
    }
    const int maxn = 1000010;
    int f[maxn],g[maxn],h[maxn];
    int main(){
    	int n;read(n);
    	int m = 0;
    	for(int i=1;i<=n;++i){
    		read(f[i]);
    		if(f[i] == i){
    			h[++m] = i;	
    			g[i] = m;
    		}
    	}
    	for(int i=1;i<=n;++i){
    		if(i != f[i]){
    			if(f[f[i]] != f[i]) return puts("-1");
    			else g[i] = g[f[i]];
    		}
    	}
    	printf("%d
    ",m);
    	for(int i=1;i<=n;++i){
    		printf("%d",g[i]);
    		if(i != n) putchar(' ');
    		else putchar('
    ');
    	}
    	for(int i=1;i<=m;++i){
    		printf("%d",h[i]);
    		if(i != m) putchar(' ');
    		else putchar('
    ');
    	}
    	return 0;
    }
    

    Problem E. Tree Folding

    题目大意

    定义一种在树上的操作:选中树上的一条点数为奇数路径,取路径的中点,使左右两条路径剩余的点不含有除路径上的点的邻居。此时可以去掉两条路径中的一条路径(中点保留).问最后能否把这个树化成一条链.若能输出链的最小边数。((n leq 10^5))

    题解

    题目是个谜
    读了半天才读懂。。。
    其实就是选择一个点,然后若这个点的两个子树是长度相同的链
    就可以消掉两个子树中的一个
    我们发现所有长度相同的是链的子树一定能合并
    而且这道题很好dp,于是我们使用树形dp来解决这个问题
    自底向上更新时,首先我们知道所有的子树都必须转化成链,这样答案才有可能
    转化成链的充要条件是子树的链只有一种长度,因为有两种长度及以上就要分叉了
    不过如果只有两种长度的话,那么这个点作为根也是可能可行的,于是我们需要找到一个这样的点尝试使其作为根.
    而如果有两种以上的长度的话就不用想了,问题无解。
    如果只有一种长度的话我们就直接将长度+1然后向上更新即可
    对于长度的判重本来写了个Hash_map的,结果炸了,改成了set..

    Code

    #include <set>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    inline void read(int &x){
    	x=0;char ch;bool flag = false;
    	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
    }
    const int maxn = 200010;
    struct Edge{
    	int to,next;
    }G[maxn<<1];
    int head[maxn],cnt;
    void add(int u,int v){
    	G[++cnt].to = v;
    	G[cnt].next = head[u];
    	head[u] = cnt;
    }
    inline void insert(int u,int v){
    	add(u,v);add(v,u);
    }
    int rt = 1,rec;
    bool flag = false;
    #define v G[i].to
    int dfs(int u,int f){
    	set<int>s;
    	for(int i = head[u];i;i=G[i].next){
    		if(v == f) continue;
    		int x = dfs(v,u);
    		if(x == -1) return -1;
    		s.insert(x+1);
    	}
    	if( (s.size() == 2 && f) ){rt = u;return -1;}
    	if( s.size() > 2 ){flag = true;return -1;}
    	if(s.size() == 0) return 0;
    	set<int>::iterator it = s.begin();
    	int x = *it;
    	if(s.size() == 1) return x;
    	return x + (*(++it));
    }
    #undef v
    int main(){
    	int n;read(n);
    	for(int i=1,u,v;i<n;++i){
    		read(u);read(v);
    		insert(u,v);
    	}
    	int ans = dfs(1,0);
    	if(flag) return puts("-1");
    	flag = false;rec = rt;
    	if(rt != 1) ans = dfs(rt,0);
    	if(flag || rec != rt) return puts("-1");
    	while(ans && !(ans&1)) ans >>= 1;
    	printf("%d
    ",ans);
    	getchar();getchar();
    	return 0;
    }
    

    Problem F. Souvenirs

    题目大意

    给定一个长为(n)的序列,每次询问区间([l,r])内的任意两个元素的最小的_差值的绝对值_.((n leq 10^5,m leq 3*10^5))

    题解

    这道题我想了不少做法.

    algorithm 1:

    使用一颗线段树来维护区间内最小元素差
    向上更新时使用归并排序的方式合并.(这好像叫做划分树)
    预处理没有问题,问题在于查询的时候无法简单合并区间...
    所以在查询的时候考虑使用std::set来解决这个东西
    利用set进行启发式合并可以做到(O(mlog^3n))
    做点底层优化应该能过@WC2017的某松

    algorithm 2:

    我们仍然使用一颗线段树来维护区间内的最小元素差
    但是我们先不统计出所有的答案
    只在线段树的节点内存上某个点代表的区间所包含的所有元素
    我们将所有的询问按第一关键字为l,第二关键字为r排序
    然后我们从序列右端点开始向左扫描
    依次考虑每个点对这个点到序列端点的区间所造成的影响
    每次处理影响我们就类比于线段树的区间修改操作
    修改线段树上每个节点内存储的最小元素差
    但是这样我们就发现:现在线段树中的点已经不满足初始的定义了
    因为对于所有的线段树中的点所代表的左区间其实都变成了当前扫描线所在的位置
    这恰恰是我们对于询问排序的目的。
    我们发现,在这种顺序下的询问中,上述错误不会带来任何影响
    只要我们在扫描线扫描到某一个点的左端点的时候立即查询即可
    因为右边界仍是满足的。
    然后你需要一个set来剪枝加速,不然会Time limit exceeded on test 7

    Code

    #include <set>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    inline void read(int &x){
    	x=0;char ch;bool flag = false;
    	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
    }
    const int maxn = 100010;
    const int maxm = 300010;
    int minn[maxn<<2];
    int tmp[maxn],a[maxn];
    int L,R,val,nw_min,n;
    set<int>T[maxn<<2];
    void build(int rt,int l,int r){
    	minn[rt] = 0x7f7f7f7f;
    	for(int i=l;i<=r;++i) T[rt].insert(a[i]);
    	if(l == r) return;
    	int mid = l+r >> 1;
    	build(rt<<1,l,mid);build(rt<<1|1,mid+1,r);
    }
    int query(int rt,int l,int r){
    	if(L <= l && r <= R) return minn[rt];
    	int mid = l+r >> 1;
    	if(R <= mid) return query(rt<<1,l,mid);
    	if(L >  mid) return query(rt<<1|1,mid+1,r);
    	return min(query(rt<<1,l,mid),query(rt<<1|1,mid+1,r));
    }
    inline bool exit_it(int rt){
    	set<int>::iterator it = T[rt].lower_bound(val);
    	int y = it == T[rt].end() ? -1 : *it;
    	int x = -1;
    	if(it != T[rt].begin()) x = *(--it);
    	if( (y == -1 || abs(y-val) >= nw_min) && (x == -1 || (abs(x-val) >= nw_min) ) ){
    		nw_min = min(nw_min,minn[rt]);
    		return true;
    	}return false;
    }
    void cacu(int rt,int l,int r){
    	if(l == r){
    		minn[rt] = min(minn[rt],abs(val - a[l]));
    		nw_min = min(nw_min,minn[rt]);
    		return ;
    	}
    	if(exit_it(rt)) return;
    	int mid = l+r >> 1;
    	if(L <= mid) cacu(rt<<1,l,mid);
    	if(R >  mid) cacu(rt<<1|1,mid+1,r);
    	minn[rt] = min(minn[rt<<1],minn[rt<<1|1]);
    }
    struct Node{
    	int l,r,id;
    	bool friend operator < (const Node &a,const Node &b){
    		return !(a.l == b.l ? a.r < b.r : a.l < b.l );
    	}
    }q[maxm];
    int anss[maxm];
    int main(){
    	read(n);
    	for(int i=1;i<=n;++i) read(a[i]);
    	int m;read(m);
    	build(1,1,n);
    	for(int i=1;i<=m;++i){
    		read(q[i].l);read(q[i].r);
    		q[i].id = i;	
    	}sort(q+1,q+m+1);
    	for(int i=1,p=n;i<=m;++i){
    		while(p >= q[i].l){
    			L = p+1;R = n;val = a[p];nw_min = 0x7f7f7f7f;
    			if(L <= R) cacu(1,1,n);
    			--p;
    		}
    		L = q[i].l;R = q[i].r;
    		anss[q[i].id] = query(1,1,n);
    	}
    	for(int i=1;i<=m;++i) printf("%d
    ",anss[i]);
    	getchar();getchar();
    	return 0;
    }
    

    Problem G. Math, math everywhere

    这道题实在不会...

    orz做出来的大爷们

    #include <set>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    typedef long long ll;
    inline void read(int &x){
    	x=0;char ch;bool flag = false;
    	while(ch=getchar(),ch<'!');if(ch == '-') ch=getchar(),flag = true;
    	while(x=10*x+ch-'0',ch=getchar(),ch>'!');if(flag) x=-x;
    }
    int main(){
    	while(1) printf("orz
    ");
    	getchar();getchar();
    	return 0;
    }
    
  • 相关阅读:
    ZCMU训练赛-H(模拟)
    ZCMU训练赛-B(dp/暴力)
    ZCMU训练赛-A(模拟)
    HDU 2045 LELE的RPG难题(递推)
    HDU 2044 一只小蜜蜂(递归)
    HDU 2050 折线分割平面(转)
    对递归的理解归纳(转)
    漫谈递归思想(转)
    2017中南大学暑期集训day1 : debug&STL-A
    探寻宝藏--河南省第六届大学生程序设计竞赛
  • 原文地址:https://www.cnblogs.com/Skyminer/p/6404613.html
Copyright © 2020-2023  润新知