Codeforces Round 319 # div.1 & 2 解题报告

Div. 2

Multiplication Table (577A)

题意:

给定n行n列的方阵,第i行第j列的数就是i*j,问有多少个格子上的数恰为x。

1<=n<=10^5, 1<=x<=10^9

题解:

送分题…对于每一行,判断是否存在数x即可…也可以枚举x的因子判断是否出现在表内…

#include<cstdio>#include<cstring>inlineint read(){int s =0;char c;while((c=getchar())<‘0‘||c>‘9‘);do{s=s*10+c-‘0‘;}while((c=getchar())>=‘0‘&&c<=‘9‘);return s;}int n,i,x,ans;int main(){
	n = read(); x = read();for(i=1;i<=n;i++)if(x%i==0)if(x/i<=n) ans++;
	printf("%d\n",ans);return0;}

Modulo Sum (577B)

题意:

给定长度为n的数列,判断是否存在和可以被m整除的子序列

1<=n<=10^6, 2<=m<=10^3, 0<=ai<=10^9

题解:

其实我看到这个数据范围以为是暴力的…

结果确实很多人拿O(nm)的暴力过了(只能说CF的评测机太快了)

我一开始写的是用bitset压位暴力,

用b[i]表示可以得到余数i,那么加入一个新的数就可以把所有为1的b[i]转移到b[(i+a[x])%m],

于是我就拿bitset的位运算来做…

(既然别人暴力都过了,我的暴力当然也能过去,其实复杂度上来看也是可以过的)

不过其实并不用考虑大数据…

考虑前缀和(这题求的是子序列而不是子段,但是显然子段也是一个子序列,存在子段也代表答案是YES)

我们知道模m的剩余系大小为m,根据抽屉原理,当n>=m+1时,必定有两个元素关于模m同余。

而如果前缀和中 sum[l] ≡ sum[r] (mod m),说明子段(l,r]就是满足条件的子序列。

所以只要n>m,答案就是YES。

所以实际复杂度就降为O(m^2)了

#include<cstdio>#include<cstring>#include<bitset>usingnamespace std;inlineint read(){int s =0;char c;while((c=getchar())<‘0‘||c>‘9‘);do{s=s*10+c-‘0‘;}while((c=getchar())>=‘0‘&&c<=‘9‘);return s;}int n,m,i,a[1010];
bitset<1024> bs,tmp;bool sol(){if(n>m)return1;for(i=1;i<=n;i++){
		tmp = bs<<a[i];
		bs |= bs>>(m-a[i]);
		bs |= tmp;
		bs[a[i]]=1;if(bs.test(0))return1;}return0;}int main(){	n = read(); m = read();if(n<=m)for(i=1;i<=n;i++) a[i]= read()%m;
	printf(sol()?"YES\n":"NO\n");return0;}

Div. 1

Vasya and Petya‘s Game (576A) (577C)

题意:

未知整数x∈[1,n],每次询问可以知道x是否被某数y整除,问最少需要问多少次才可以确定x的具体数值。

1<=n<=10^3

题解:

我自己的做法是,

从小到大遍历1~n里的每一个数,

如果它的因子中所有被询问的数的最小公倍数t不等于自身,则说明需要询问这个数。

因为如果t等于自身,所有询问结果都是“是”的情况就可以唯一确定这个数。

否则就无法区分t与当前数。

而官方题解的做法是,因为如果不询问p^k就无法区分p^(k-1)和p^k,所以每个p^k(k>0)都要询问。

后来我仔细想了一下,这两个做法其实是等价的。

因为因子中所有被询问的数的最小公倍数不等于自身的,只有底数为质数的幂。

具体来说的话,

1、显然质数是要询问的

2、当前数为合数,且是底数为质数的幂

如果当前数是p^a,因为只有形如p^b (b|a, b<a) 的因子,

所以它们的最小公倍数一定小于p^a,也就是说p^a要被询问。

3、当前数为合数,且不是底数为质数的幂

根据算术基本定理,合数n可以表示成 p1^a1 * p2^a2 * … * pk^ak,

显然 p1^a1、p2^a2…pk^ak 两两互质。

又因为所有pi^ai会被询问,所以这些数的最小公倍数一定就是n本身,

所以此时不用询问。

回到这题,只要筛出素数,并输出它的幂即可。

复杂度O(n)

#include<cstdio>#include<cstring>int n,i,j,s,t,tt,a[1003],pr[1003],ps;bool b[1003];int main(){	scanf("%d",&n); s =0;for(i=2;i<=n;i++){if(!b[i]){
			pr[++ps]= i;for(j=i;j<=n;j*=i) a[++s]= j;}for(j=1;j<=ps&&i*pr[j]<=n;j++){
			b[i*pr[j]]=1;if(i%pr[j]==0)break;}}
	printf("%d\n",s);for(i=1;i<s;i++) printf("%d ",a[i]);if(s) printf("%d\n",a[s]);return0;}

Invariance of Tree (576B) (577D)

题意:

给定长为n的序列p1..pn,要求输出一棵树满足:u与v相连 当且仅当 p[u]与p[v]相连

有解输出YES和任意一个解的树上的所有边,无解输出NO。

1<=n<=10^5

题解:

构造…

这题的突破口就是题目名字…

重点在于找到可行解中进行置换后不变的东西。

(不变的是一个点)如果存在一个数a=p[a],那么把所有其它点连到这个点就是一个可行解。

(不变的是一条边)也就是说存在两个数,满足a=p[a]且b=p[b]。这个时候的情况会稍微复杂一点。

因为虽然这条边不动,但是两个端点交换了位置,如果边的两端点的子树不同,置换后就不满足要求。

所以同时要满足这条边两端子树中的节点也恰好交换位置。

所以可以找到每一个环,

把环上奇数位置的点连到a,偶数位置的连到b(置换后ab互换,同时这些点也互换位置)

不过这样做,在存在奇数个元素的环的时候就不行了,这时无法构造。

(注意这时如果存在第一种情况的构造就可以按照第一种情况来)

#include<cstdio>#include<cstring>inlineint read(){int s =0;char c;while((c=getchar())<‘0‘||c>‘9‘);do{s=s*10+c-‘0‘;}while((c=getchar())>=‘0‘&&c<=‘9‘);return s;}constint N =100010;int n,i,j,nx[N],st,st2,l,tmp,ev;bool b[N];int main(){	n = read();for(i=1;i<=n;i++) nx[i]= read();
	l =-1; ev =1;for(i=1;i<=n;i++){for(j=i,tmp=0;!b[j];j=nx[j],tmp++) b[j]=1;if(l!=1&&tmp==1) st = i, l =1;if(l==-1&&tmp==2) st = i, st2 = nx[i], l =2;if(tmp&1) ev =0;}if(l==-1||(l==2&&!ev)) printf("NO\n");else{
		printf("YES\n");if(l==1){for(i=1;i<=n;i++)if(i!=st) printf("%d %d\n",st,i);}else{
			printf("%d %d\n",st,st2);
			memset(b,0,sizeof b);for(i=1;i<=n;i++){if(i==st||i==st2)continue;for(j=i,tmp=0;!b[j];j=nx[j],tmp^=1) b[j]=1, printf("%d %d\n",j,tmp?st2:st);}}}return0;}

Points on Plane (576C) (577E)

题意:

给定n个点,求一条经过所有点的路径,它的总曼哈顿距离<25*10^8

1<=n<=10^6, 0<=x,y<=10^6

题解:

完全的乱搞题…一开始感觉无从下手…样例也是用来搞笑的…

正确的做法是按照[x/1000]分块…每块中按照y坐标排序…

然后交替从高到低、从低到高地走,然后就构造出答案了。

#include<cstdio>#include<cstring>#include<algorithm>#include<vector>usingnamespace std;typedeflonglong lint;constint N =1000010;
lint x[N],y[N],i,j,n;bool p;
vector<int> vct[1010];
vector<int>::iterator it;
vector<int>::reverse_iterator rit;inlineint read(){int s =0;char c;while((c=getchar())<‘0‘||c>‘9‘);do{s=s*10+c-‘0‘;}while((c=getchar())>=‘0‘&&c<=‘9‘);return s;}inlineint abs(int a){return a>0?a:-a;}bool cmp(constint&a,constint&b){return y[a]<y[b];}int main(){	n = read();for(i=1;i<=n;i++){
		x[i]= read(), y[i]= read();
		vct[x[i]/1000].push_back(i);}for(i=0;i<=1000;i++){if(vct[i].empty())continue;
		p^=1;
		sort(vct[i].begin(),vct[i].end(),cmp);if(p)for(rit=vct[i].rbegin();rit!=vct[i].rend();rit++) printf("%d ",(*rit));elsefor(it=vct[i].begin();it!=vct[i].end();it++) printf("%d ",(*it));}return0;}

Flights for Regular Customers (576D)

题意:

给定一个有向图,图上的边有限制条件,必须满足经过的边数(包括相同)达到限制条件才可以同行。

存在自环。

2<=n<=150, 1<=m<=150

题解:

150…想想发现好像不是网络流…

嗯就DP好了。f[i][j]表示i步后能否在j这个点。

很显然这个DP可以用转移矩阵跑快速幂来弄…

然后对于从小到大的每个限制条件,

如果满足较大的条件时仍然不能到达,那么只满足较小的条件时也一定不能到达。

看到这种性质…我就直接二分了…然而因为转移时也有m的因子,所以二分没什么用…

于是写了从小到大枚举满足的条件,如果满足后能够到达,就在这一个条件和下一个条件之间二分…

然后就有了复杂度爆炸的解法…被卡了很多次…尽管是CF上的4s…

主要有两点优化,都是在矩阵乘法时优化,(因为矩阵是01矩阵所以可以优化)

一个是改变矩阵乘法时的循环顺序,改为i-k-j循环,那么如果a[i][k]==0,则所有a[i][k]&b[k][j]都是0,这时可以continue掉…

另一个是在上面加上bitset…等于直接压位然后位运算来算矩阵了…

(其实加上第一个之后我就已经1s内过了)

#include<cstdio>#include<cstring>#include<algorithm>#include<bitset>usingnamespace std;inlineint read(){int s =0;char c;while((c=getchar())<‘0‘||c>‘9‘);do{s=s*10+c-‘0‘;}while((c=getchar())>=‘0‘&&c<=‘9‘);return s;}constint N =155;typedef bitset<N> bs;int n,m,i,j,po,l,r,mid,stp[N],dt,ie,ans,ymd;struct eg{int aa,bb,d;}e[N];struct mrx{
	bs nu[150];void clr(){ memset(nu,0,sizeof nu);}friend mrx operator*(const mrx &a,const mrx &b){
		mrx c; c.clr();for(int i=0;i<n;i++)for(int k=0;k<n;k++)if(a.nu[i][k])
				c.nu[i]|= b.nu[k];return c;}}ym,tm,sm,tmp;bool cmp(const eg &a,const eg &b){return a.d<b.d;}
mrx powmrx(mrx a,int b){
	mrx ans; ans.clr();for(int i=0;i<n;i++) ans.nu[i][i]=1;for(;b;b>>=1){if(b&1) ans = ans*a;
		a = a*a;}return ans;}void sol(int curp){
	l =0; r = e[stp[curp+1]].d-e[stp[curp]].d-1;while(l<=r){
		mid =(l+r)>>1;
		sm = ym*powmrx(tm,mid);if(sm.nu[0][n-1])
			ans = mid, r = mid-1;else
			l = mid+1;}}int main(){#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);#endif
	n = read(); m = read();for(i=1;i<=m;i++) e[i].aa = read(), e[i].bb = read(), e[i].d = read();
	std::sort(e+1,e+1+m,cmp);
	e[0].d =-1;for(i=1;i<=m;i++)if(e[i].d!=e[i-1].d) stp[++dt]= i;
	e[stp[dt+1]= m+1].d =1000000000+n;
	po =1; tm.nu[n-1][n-1]=1; ym.nu[0][0]=1;
	ans =-1;if(e[1].d==0)for(i=1;i<=dt;i++){for(j=stp[i],ie=stp[i+1];j<ie;j++) tm.nu[e[j].aa-1][e[j].bb-1]=1;
			tmp = ym*powmrx(tm,e[stp[i+1]].d-e[stp[i]].d);if(tmp.nu[0][n-1]){
				sol(i);if(ans!=-1){ ans += e[stp[i]].d;break;}}
			ym = tmp;}if(ans==-1) printf("Impossible\n");else printf("%d\n",ans);return0;}

Painting Edges (576E)

题意:

给定n点m边的无向图,有q次询问,k种颜色。

对于每个询问,如果操作后对应颜色能够构成二分图,则改变颜色并输出YES

否则不改变颜色,并输出NO

2<=n<=10^5

1<=m,q<=10^5

1<=k<=50

题解:

线段树+暴力维护并查集…

并查集维护的方法就是维护奇偶性。在不同集合之间的边显然可以,同一集合内奇偶性不同的边也可以。

用线段树存所有的询问。题目最大的难点在于某种颜色对应的边被删除…

换一种角度看,就是这条边的颜色会保持到下次改变这条边之前。

所以拿线段树来暴力维护并查集状态就好了…

求答案就是按1~q在线段树的叶节点跑。

(这种维护并查集不能路径压缩,所以要按秩合并,否则超时非常严重)

(深深地体会到了这种差别)

#include<cstdio>#include<cstdlib>#include<cstring>#include<algorithm>#include<vector>usingnamespace std;constint N =500003;typedef pair<int,int> pii;int n,m,i,j,k,Q,ql,qr,qc,qe,nx[N],tnx[N],rcol[N];
pii e[N],q[N];
vector<pii> mgv[1100000];struct revdt
{int c,p,f,z,s;
	revdt(int _c,int _p,int _f,int _z,int _s){c=_c,p=_p,f=_f,z=_z,s=_s;}};typedef vector<revdt> revvct;struct revdsu
{int f[N],z[N],s[N];
	pii findf(int a){int ss=0;while(a!=f[a]) ss^= s[a], a = f[a];return make_pair(a,ss);}bool dis(int a,int b){return findf(a)!=findf(b);}void merge(int a,int b,int col,revvct &vct){
		pii pa = findf(a), pb = findf(b);int fa = pa.first, fb = pb.first;if(fa==fb)return;bool fl =0;if(z[fa]==z[fb]) z[fa]++, fl =1;if(z[fa]>z[fb]) swap(fa,fb);
		vct.push_back(revdt(col,fa,f[fa],z[fa]-(fl?1:0),s[fa]));
		f[fa]= fb; s[fa]= pa.second^pb.second^1;}}osu[51]; //osu是来卖萌的inlineint read(){int s =0;char c;while((c=getchar())<‘0‘||c>‘9‘);do{s=s*10+c-‘0‘;}while((c=getchar())>=‘0‘&&c<=‘9‘);return s;}void add(int p,int l,int r){if(ql<=l&&r<=qr){
		mgv[p].push_back(make_pair(qc,qe));return;}int m =(l+r)>>1;if(ql<=m) add(p+p,l,m);if(qr>m) add(p+p+1,m+1,r);}void query(int p,int l,int r){
	revvct vct;for(vector<pii>::iterator it=mgv[p].begin();it!=mgv[p].end();it++)
		osu[it->first].merge(e[it->second].first,e[it->second].second,it->first,vct);if(l>=r){
		qe = q[l].first;int a = e[qe].first, b = e[qe].second, co = q[l].second;if(osu[co].dis(a,b)) printf("YES\n"), rcol[qe]= co;else printf("NO\n");
		ql = l+1, qr = nx[l]-1, qc = rcol[qe];if(rcol[qe]) add(1,1,Q);}else{int m =(l+r)>>1;
		query(p+p,l,m);
		query(p+p+1,m+1,r);}while(!vct.empty()){
		revdt it = vct.back();
		osu[it.c].f[it.p]= it.f;
		osu[it.c].z[it.p]= it.z;
		osu[it.c].s[it.p]= it.s;
		vct.pop_back();}}int main(){#ifndef ONLINE_JUDGE
	freopen("in.txt","r",stdin);#endif
	n = read(); m = read(); k = read(); Q = read();for(i=1;i<=n;i++)for(j=1;j<=k;j++) osu[j].f[i]= i;for(i=1;i<=m;i++) e[i].first = read(), e[i].second = read(), tnx[i]=Q+1;for(i=1;i<=Q;i++) q[i].first = read(), q[i].second = read();for(i=Q;i;i--){ nx[i]= tnx[q[i].first]; tnx[q[i].first]= i;}
	query(1,1,Q);return0;}
时间: 2024-11-02 07:50:23

Codeforces Round 319 # div.1 & 2 解题报告的相关文章

[Codeforces Round #194 (Div. 2)] Secret 解题报告 (数学)

题目链接:http://codeforces.com/problemset/problem/334/C 题目: 题目大意: 给定数字n,要求构建一个数列使得数列的每一个元素的值都是3的次方,数列之和S大于n,且删掉数列中的任意一个元素数列之和都会小于n,最小化这个数列的长度 题解: 我们考虑从小到大枚举k,取最小的k,使得,答案就是$n/3^k+1$ 为什么呢? 我们考虑一个合法的数列,其中最小的元素是A,那么S一定是A的倍数.假设n是A的倍数,又S>n,那么S-A>=n,这样的话去掉A这个数

Codeforces Round #319 (Div. 2) C Vasya and Petya&#39;s Game

因为所有整数都能被唯一分解,p1^a1*p2^a2*...*pi^ai,而一次询问的数可以分解为p1^a1k*p2^a2k*...*pi^aik,这次询问会把所有a1>=a1k && a2 >= a2k &&... a3 >= a3k的数从原来的集合中分开.ai表示pi的幂. 那么只有当这个数的素因子的最大幂都被询问过一次,这个数才能确定.因此答案是所有的不大于n的只有一个素因子的数. #include<bits/stdc++.h> using

Codeforces Round #319 (Div. 2) D

E A tree of size n is an undirected connected graph consisting of n vertices without cycles. Consider some tree with n vertices. We call a tree invariant relative to permutation p = p1p2... pn, if for any two vertices of the tree u andv the condition

CodeForce---Educational Codeforces Round 3 The best Gift 解题报告

对于这题笔者认为可以用数学排列来算,但是由于笔者很懒所以抄了一段大神的代码来交个大家了, 这位大神的基本想法就是通过记录各类书的数量,再暴力破解: 下面贴出这位大神的代码吧: 1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 6 int h[15]; 7 int main() 8 { 9 int n,m; 10 scanf("%d%d&q

Codeforces Round #319 (Div. 2) B Modulo Sum

直接O(n*m)的dp也可以直接跑过. 因为上最多跑到m就终止了,因为sum[i]取余数,i = 0,1,2,3...,m,会有m+1种可能,m的余数只有m种必然有两个相同. #include<bits/stdc++.h> using namespace std; const int maxn = 1e3+5; int cnt[maxn]; bool dp[maxn][maxn]; #define Y { puts("YES"); return 0; } int main(

Codeforces Round #319 (Div. 2) E - Points on Plane

题目大意:在一个平面里有n个点,点坐标的值在1-1e6之间,让你给出一个遍历所有点的顺序,要求每个点走一次,且 曼哈顿距离之和小于25*1e8. 思路:想了一会就有了思路,我们可以把1e6的x,y坐标都分成2000份,每份500,然后这样整个平面就被分成 了2000*2000个区域,然后按区域输出点就行了. 1 #include<bits/stdc++.h> 2 using namespace std; 3 int n; 4 vector<int> ans[2005][2005];

codeforces 576c// Points on Plane// Codeforces Round #319(Div. 1)

题意:有n个点,找到一个顺序走遍这n个点,并且曼哈顿距离不超过25e8. 由于给的点坐标都在0-1e6之间.将x轴分成1000*1000,即1000长度为1块.将落在同一块的按y排序,编号为奇的块和偶的块一个升序,一个降序.有3个量值得关注.一是同一块中x的变化量,其实是不超过1000*n1,n1是第1块中点的数量.那么1000*n1+1000*n2......=1000*n<1e9.后两个量是同一块中y的高度差,另一个是本块最后一个和另一块第一个的高度差.这种做法下相邻两块这两个高度差的和是小

Codeforces Round #259 (Div. 2) 解题报告

终于重上DIV1了.... A:在正方形中输出一个菱形 解题代码: 1 // File Name: a.cpp 2 // Author: darkdream 3 // Created Time: 2014年08月01日 星期五 23时27分55秒 4 5 #include<vector> 6 #include<set> 7 #include<deque> 8 #include<stack> 9 #include<bitset> 10 #inclu

Codeforces Round #262 (Div. 2)解题报告

详见:http://robotcator.logdown.com/posts/221514-codeforces-round-262-div-2 1:A. Vasya and Socks   http://codeforces.com/contest/460/problem/A 有n双袜子,每天穿一双然后扔掉,每隔m天买一双新袜子,问最多少天后没有袜子穿.. 简单思维题:以前不注重这方面的训练,结果做了比较久,这种题自己边模拟边想.不过要多考虑trick ```c++ int main(){ i