ZOJ Monthly, March 2018 题解

 

题目链接

 

A. ZOJ 4004 - Easy Number Game

首先肯定是选择值最小的 $2*m$ 进行操作,这些数在操作的时候每次取一个最大的和最小的相乘是最优的。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 100010;
int T, n, m;
long long a[maxn];

int main() {
  scanf("%d", &T);
  while(T--) {
    scanf("%d%d", &n, &m);
    long long sum = 0;
    for(int i = 0; i < n; i ++) {
      scanf("%lld", &a[i]);
    }
    sort(a, a+n);
    
    int first = 0, last = m * 2 - 1;
    while(first < m) {
      sum = sum + a[first] * a[last];
      first++;
      last--;
    }
    printf("%lld\n", sum);
  }
  return 0;
}

 

B. ZOJ 4005 -  Lucky Man

找规律会发现就是求 $\left\lfloor \sqrt { n }  \right\rfloor $ 的奇偶性。从这里学大数开根号。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int l;char ans[1000];int num=0;
int work(int o,char *O,int I)
{
  char c,*D=O;
  if(o>0)
  {
    for(l=0;D[l];D[l++]-=10)
    {
      D[l++]-=120;
      D[l]-=110;
      while(!work(0,O,l))
        D[l]+=20;
      //putchar((D[l]+1032)/20);
      ans[num++]=(D[l]+1032)/20;
    }
    //putchar(10);
    //ans[num++]='1';ans[num++]='0';
  }
  else
  {
    c=o+(D[I]+82)%10-(I>l/2)*(D[I-l+I]+72)/10-9;
    D[I]+=I<0 ? 0 : !(o=work(c/10,O,I-1))*((c+999)%10-(D[I]+92)%10);
  }
  return o;
}

int main()
{
  char s[1200];
  int t;scanf("%d",&t);
  while(t--){
    num=0;
    s[0]='0';
    scanf("%s",s+1);
    if(strlen(s)%2 == 1)
      work(2,s+1,0);
    else
      work(2,s,0);
    ans[num]='\0';
    int tmp=ans[num-1]-'0';
    if(tmp&1) printf("1\n");
    else printf("0\n");
  }
  return 0;
}

 

C. ZOJ 4006 - Travel along the Line

假设往左走了 $x$ 步,停留了 $y$ 步,往右走了 $z$ 步,这种情况下的概率为 ${ C }_{ n }^{ x }{ C }_{ n-x }^{ y }{ \left( \frac { 1 }{ 4 }  \right)  }^{ x+z }{ \left( \frac { 1 }{ 2 }  \right)  }^{ y }$。枚举 $x$ 之后,$y$ 和 $z$ 都能确定。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 6e5 + 10;
const int INF = 0x7FFFFFFF;
const long long mod = 1e9 + 7;

int T, n, m;
long long f[maxn], po[maxn];

long long extend_gcd(long long a,long long b,long long &x,long long &y) {
	if(a==0&&b==0) return -1;//无最大公约数
	if(b==0){x=1;y=0;return a;}
	long long d=extend_gcd(b,a%b,y,x);
	y-=a/b*x;
	return d;
}

long long mod_reverse(long long a,long long n) {
	long long x,y;
	long long d=extend_gcd(a,n,x,y);
	if(d==1) return (x%n+n)%n;
	else return -1;
}

long long C(int a, int b) {
	long long fz = f[a];
	long long fm = f[a - b] * f[b] % mod;
	return fz * mod_reverse(fm, mod) % mod;
}

void init() {
	po[0] = 1LL;
	f[0] = 1LL;
	for(int i = 1; i < maxn; i ++) {
		po[i] = (po[i - 1] * 2LL) % mod;
		f[i] = (f[i - 1] * i) % mod;
	}
}

int main() {
#ifdef ZHOUZHENTAO
	freopen("test.in", "r", stdin);
#endif

	init();
	scanf("%d", &T);
	while(T --) {
		scanf("%d%d", &n, &m);

		long long ans = 0;
		for(int x = 0; x <= n; x ++) {
			int y = n - x - m - x;
			int z = m + x;
			if(y < 0 || y > n) continue;
			if(z < 0 || z > n) continue;
			long long fz = C(n, x) * C(n - x, y) % mod;
			long long fm = po[2 * (x + z) + y];
			long long tmp = fz * mod_reverse(fm ,mod) % mod;
			ans = (ans + tmp) % mod;
		}

		printf("%lld\n", ans);
	}

	return 0;
}

 

D. ZOJ 4007 - Machine Learning on a Tree

由于每一个节点的 $x_i$ 都相同,因此 $w_{ij}$ 均为 1。总的来说就是让你安排每个节点的权值,如果某条边两端点权值不同,$value$ 就加 1,问 $value$ 的最小值是多少。

将树有根化后进行 dp,$f[i][j]$ 表示 $i$ 节点的权值设置为 $j$,以 $i$ 为根的子树的最小 $value$ 值。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e5 + 10;
const int INF = 0x7FFFFFFF;

int h[maxn], to[maxn], nx[maxn], sz;
int y[maxn];
int T, n;
int dp[maxn][2], f[maxn];

void add(int x, int y) {
	to[sz] = y;
	nx[sz] = h[x];
	h[x] = sz ++;
}

void dfs(int x) {
	f[x] = 1;
	if(y[x] == -1) dp[x][0] = dp[x][1] = 0;
	else dp[x][y[x]] = 0;	
	for(int i = h[x]; i != -1; i = nx[i]) {
		if(f[to[i]]) continue;
		dfs(to[i]);
		if(y[x] != -1) {
			dp[x][y[x]] += min(dp[to[i]][y[x]], dp[to[i]][y[x] ^ 1] + 1); 
		} else {
			dp[x][0] += min(dp[to[i]][0], dp[to[i]][1] + 1);
			dp[x][1] += min(dp[to[i]][0] + 1, dp[to[i]][1]);
		}
	}
}

int main() {
#ifdef ZHOUZHENTAO
	freopen("test.in", "r", stdin);
#endif

	int T;
	scanf("%d", &T);
	while(T --) {
		scanf("%d", &n);
		for(int i = 1; i <= n; i ++) {
			scanf("%d", &y[i]);
			h[i] = -1;
			dp[i][0] = dp[i][1] = 5000000;
			f[i] = 0;
		}
		sz = 0;
		for(int i = 1; i < n; i ++) {
			int x, y;
			scanf("%d%d", &x, &y);
			add(x, y);
			add(y, x);
		}	
		dfs(1);
		for(int i = 1; i <= n; i ++) {
			//  cout << i << " " << dp[i][0] << " " << dp[i][1]<< endl;
		}
		printf("%d\n", min(dp[1][0], dp[1][1]));
	}


	return 0;
}

 

E. ZOJ 4008 - Yet Another Tree Query Problem

树上询问编号在 $[L,R]$ 范围的节点和边构成了几个连通块。首先 $[L, R]$ 有 $R-L+1$ 个节点,如果我们能够计算出来 $[L, R]$ 有 $x$ 条边,那么答案就是 $R-L+1-x$。由于是树,多一条边就会少一个连通块。

计算 $[L, R]$ 中的边的条数就成了一个区间问题。题目中给出的边相当于 $n-1$ 个区间,问这些区间中有多少区间完全在 $[L,R]$ 内。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;
const int INF = 0x7FFFFFFF;

int T, n, q;
struct X {
	int L, R, tp, id;
}s[maxn * 4];

int ans[maxn * 4];
int c[maxn * 4];

int lowbit(int x) {
	return x & (-x);
}

int sum(int p) {
	int res = 0;
	while(p > 0) {
		res += c[p];
		p -= lowbit(p);
	}
	return res;
}

void update(int p) {
	while(p < 4 * maxn) {
		c[p] += 1;
		p += lowbit(p);
	}
}

bool cmp(const X& a, const X& b) {
	if(a.R != b.R) {
		return a.R < b.R;
	} else {
	  return a.tp < b.tp;
	}
}

int main() {
#ifdef ZHOUZHENTAO
	freopen("test.in", "r", stdin);
#endif

	scanf("%d", &T);
	while(T--) {
		scanf("%d%d", &n, &q);
		for(int i = 1; i < n + q; i ++) {
			scanf("%d%d", &s[i].L, &s[i].R);
			if(s[i].L > s[i].R) {
				swap(s[i].L, s[i].R);
			}
			if(i < n) s[i].tp = 0;
			else s[i].tp = 1;
			s[i].id = i;
		}
		int u = 0;
		sort(s + 1, s + n + q, cmp);

		for(int i = 1; i < n + q; i ++) {
			//		cout << s[i].L << " " << s[i].R << " " << s[i].tp << " " << s[i].id << endl;;
		}

		memset(c, 0, sizeof c);
		for(int i = 1; i < n + q; i ++) {
			if(s[i].tp == 0) {
				u ++;
				update(s[i].L);
			} else {
				ans[s[i].id] = (s[i].R - s[i].L + 1) - (u - sum(s[i].L - 1));
			}
		}

		for(int i = n; i < n + q; i ++) {
			printf("%d\n", ans[i]);
		}
	}


	return 0;
}

 

F. ZOJ 4009 - And Another Data Structure Problem

所有数据在变 48 次之后,就会变回一开始的数,因此循环节为 48,线段树每个节点存 48 种状态即可。

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;

const int mod = 99971;
const int maxn = 1e5 + 10;

int T, n, q;
int s[maxn * 4][50];
int f[maxn * 4], p[maxn * 4];
int nx[maxn];

void init() {
  for(int i = 0; i < 99971; i ++) {
    nx[i] = 1;
    for(int j = 0; j < 3; j ++) {
      long long tmp = (1LL * nx[i] * 1LL * i) % (long long)mod;
      nx[i] = (int)tmp;
    }
  }
}

void pushUp(int rt) {
  for(int i = 0; i < 48; i ++) {
    s[rt][i] = (s[2 * rt][(p[2 * rt] + i) % 48]
                + s[2 * rt + 1][(p[2 * rt + 1] + i) % 48]) % mod;
  }
  p[rt] = 0;
}

void pushDown(int rt) {
  if(f[rt] == 0) return;
  f[2 * rt] += f[rt];
  f[2 * rt + 1] += f[rt];
  p[2 * rt] = (p[2 * rt] + f[rt]) % 48;
  p[2 * rt + 1] = (p[2 * rt + 1] + f[rt]) % 48;
  f[rt] = 0;
}

void build(int l, int r, int rt) {
  p[rt] = f[rt] = 0;
  if(l == r) {
    scanf("%d", &s[rt][0]);
    s[rt][0] = s[rt][0] % mod;
    for(int i = 1; i < 48; i ++) {
      s[rt][i] = nx[s[rt][i - 1]];
    }
    return;
  }
  int mid = (l + r) / 2;
  build(l, mid, 2 * rt);
  build(mid + 1, r, 2 * rt + 1);
  pushUp(rt);
}

void update(int L, int R, int l, int r, int rt) {
  if(L <= l && r <= R) {
    p[rt] = (p[rt] + 1) % 48;
    f[rt] = f[rt] + 1;
    return;
  }
  pushDown(rt);
  int mid = (l + r) / 2;
  if(L <= mid) update(L, R, l, mid, 2 * rt);
  if(R > mid) update(L, R, mid + 1, r, 2 * rt + 1);
  pushUp(rt);
}

int sum(int L, int R, int l, int r, int rt) {
  if(L <= l && r <= R) {
    return s[rt][p[rt]];
  }
  int left = 0;
  int right = 0;
  pushDown(rt);
  int mid = (l + r) / 2;
  if(L <= mid) left = sum(L, R, l, mid, 2 * rt);
  if(R > mid) right = sum(L, R, mid + 1, r, 2 * rt + 1);
  pushUp(rt);
  return (left + right) % mod;
}


int main() {
  init();
  scanf("%d", &T);
  while(T --) {
    scanf("%d%d", &n, &q);
    build(1, n, 1);
    while(q --) {
      int op, L, R;
      scanf("%d%d%d", &op, &L, &R);
      if(op == 1) {
        update(L, R, 1, n, 1);
      } else {
        printf("%d\n", sum(L, R, 1, n, 1));
      }
    }
  }
  return 0;
}

 

G. ZOJ 4010 - Neighboring Characters

看了 cxhscst2 的题解恍然大悟。

删除连续 $k$ 个字符,相当于留下连续的 $p=len-k$ 个字符,我们可以从留下来的角度来考虑问题。

先把环拆了,也就是字符串扩大一倍,这样环上一段连续的区间就对应于字符串上一段连续的区间;同样的地,字符串上一段连续的区间也对应于环上一段连续的区间。

然后要去看是否存在长度为 $p(1\le p\le len)$ 的子串,相邻和首尾的字符均不同。

接下来感觉是本题精髓:

假设位置 1 最右能扩展到位置 $a$,保证 $[1,a]$ 相邻字符都不相同(首尾先不管)

假设位置 $a+1$ 最右能扩展到位置 $b$,保证 $[a+1,b]$ 相邻字符都不相同(首尾先不管)

假设位置 $b+1$ 最右能扩展到位置 $c$,保证 $[b+1,c]$ 相邻字符都不相同(首尾先不管)

....

假设位置 $x+1$ 最右能扩展到位置 $len$,保证 $[x+1,len]$ 相邻字符都不相同(首尾先不管)

我们把 "长度为 $p(1\le p\le len)$ 的子串,相邻和首尾的字符均不同" 的子串称为我们需要寻找的串。

那么,我们需要寻找的串要么没有,要么必然出现在 $[1,a]$,$[a+1,b]$,....,$[x+1,len]$ 其中的一个或多个中。

为什么不用考虑 $[2, ?]$,因为 ?最大也只有 $a$,所以考虑 $[1,a]$ 足够了。

下面以 $[1,a]$ 为例,之后的都是一样的操作。

要在 $[1,a]$ 中寻找是否存在长度为 $p$ 的我们需要寻找的串。首先,$[1,a]$ 中任意一个长度为 $p$ 的子串,相邻都是不同的,我们只需判断首尾。

也就是如果满足以下其中的一项,我们就找到了长度为 $p$ 的我们需要找的串。

$s[1]\neq s[p]\\ s[2]\neq s[p+1]\\ s[3]\neq s[p+2]\\ ...\\ s[a-p+1]\neq s[a]$

观察一下可以发现,不等式左边形成了一个前缀,右边是一个后缀,也就是说如果前缀不等于后缀,就有满足条件的,因此字符串 hash 判断一下前后缀是否相同即可。

复杂度的话是 $O(n)$;因为 $[1,a]$,$[a+1,b]$,....,$[x+1,len]$ 这些区间无相交部分,每个区间内枚举一遍,所以复杂度是线性的。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 2e6 + 10;
const int INF = 0x7FFFFFFF;

char s[maxn];
int ans[maxn];
long long base = 131;
long long mod = 1e9 + 7;
long long h[maxn], po[maxn];

long long H(int L, int R) {
	L ++; R ++;
	long long res = 0;
	long long tmp = h[L - 1] * po[R - L + 1] % mod;
	res = (h[R] - tmp + mod) % mod;
	return res;
}

int main() {
#ifdef ZHOUZHENTAO
  freopen("test.in", "r", stdin);
#endif

	po[0] = 1LL;
	for(int i = 1; i < maxn; i ++) {
		po[i] = (po[i - 1] * base) % mod;
	}

	int T;
	scanf("%d", &T);
	while(T --) {
	  scanf("%s", s);
		int len = strlen(s);
		for(int i = len; i < 2 * len; i ++) {
		  s[i] = s[i - len];
			s[i + 1] = 0;
		}
		len = 2 * len;

		for(int i = 0; i < len; i ++) {
			h[i + 1] = h[i] * base % mod;
			h[i + 1] = (h[i + 1] + s[i]) % mod;
		}
			
		//printf("%s\n", s);
		for(int i = 0; i <= len; i ++) {
			ans[i] = 0;
		}

		for(int i = 0, j = 0; i < len; i = j + 1) {
			for(j = i; j < len && j + 1 < len && s[j] != s[j + 1]; j ++) {}
			//cout << i << " " << j << endl;
			int t = j - i + 1;
			t = min(t, len / 2);
			for(int x = 1; x <= t; x ++) {
			  if(x == 1) {
					ans[x] = 1;
				} else if(H(i, j - x + 1) != H(i + x - 1, j)) {
					ans[x] = 1;
				}
			}
		}

		for(int i = len / 2; i >= 1; i --) {
		  printf("%d", ans[i]);
		}
		printf("\n");
	}
	return 0;
}

 

H. ZOJ 4011 - Happy Sequence

$f[i][j]$ 表示放了 $i$ 个数,最后一个数是 $j$ 的方案数。转移的时候枚举一下约数就可以了。

#include <bits/stdc++.h>
using namespace std;

const long long mod = 1e9 + 7;
int T, n, m;
long long dp[2010][2010];
vector<int> vec[2010];

int main() {
  
  for(int i = 1; i <= 2000; i++) {
    for(int j = 1; j <= i; j ++) {
      if(i % j == 0) {
        vec[i].push_back(j);
      }
    }
  }
  
  scanf("%d", &T);
  while(T --) {
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i ++) {
      dp[1][i] = 1;
    }
    
    for(int i = 2; i <= m; i ++) {
      for(int j = 1; j <= n; j ++) {
        dp[i][j] = 0;
        for(int k = 0; k < vec[j].size(); k ++) {
          dp[i][j] = (dp[i][j] + dp[i - 1][vec[j][k]]) % mod;
        }
      }
    }
    
    long long ans = 0;
    for(int j = 1; j <= n; j ++) {
      ans = (ans + dp[m][j]) % mod;
    }
    printf("%lld\n", ans);
    
  }
  return 0;
}

 

I. ZOJ 4012 - Your Bridge is under Attack

用 KD-Tree 切割一下平面,每个节点算一下包围住这个平面的最小矩形坐标,每次询问的时候如果发现这条线和这个矩形无相交部分,直接 return。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 1e5 + 10;
const int INF = 0x7FFFFFFF;
const int demension = 2;//二维

struct node {
	int pos[demension];
	int l, r;
	int mi[2], ma[2];
}a[maxn];
int cmpDem;
int root;

int n, q, ans, A, B;

bool cmp(const node &a, const node&b) {
	return a.pos[cmpDem] < b.pos[cmpDem];
}

void Merge(int k) {
	for (int i = 0; i < demension; i++) {
		a[k].ma[i] = a[k].pos[i];
		a[k].mi[i] = a[k].pos[i];
		if (a[k].l) {
			a[k].ma[i] = max(a[k].ma[i], a[a[k].l].ma[i]);
			a[k].mi[i] = min(a[k].mi[i], a[a[k].l].mi[i]);
		}
		if (a[k].r) {
			a[k].ma[i] = max(a[k].ma[i], a[a[k].r].ma[i]);
			a[k].mi[i] = min(a[k].mi[i], a[a[k].r].mi[i]);
		}
	}
}

int build(int l, int r, int k) {
	if (l > r) return 0;
	int mid = (l + r) / 2;
	//以第mid个元素为中心排序
	cmpDem = k;
	nth_element(a + l, a + mid, a + r + 1, cmp);
	//左右子树
	a[mid].l = build(l, mid - 1, (k + 1) % demension);
	a[mid].r = build(mid + 1, r, (k + 1) % demension);
	Merge(mid);
	return mid;
}

int G(int x1, int y1, int x2, int y2, int x3, int y3) {
	if(1LL * (x2 - x1) * (y3 - y2) == 1LL * (x3 - x2) * (y2 - y1)) return 1;
	return 0;
}

int ok(int id) {
	int x1 = A, x2 = 0, x3 = a[id].pos[0];
	int y1 = 0, y2 = B, y3 = a[id].pos[1];
	return G(x1, y1, x2, y2, x3, y3);
}

struct Point   {  
	long long x;  
	long long y;
	Point(int xx, int yy) {
		x = 1LL * xx;
		y = 1LL * yy;
	}
};  

bool lineIntersectSide(Point A, Point B, Point C, Point D) {  
	// A(x1, y1), B(x2, y2)的直线方程为:  
	// f(x, y) =  (y - y1) * (x1 - x2) - (x - x1) * (y1 - y2) = 0  

	long long fC = (C.y - A.y) * (A.x - B.x) - (C.x - A.x) * (A.y - B.y);  
	long long fD = (D.y - A.y) * (A.x - B.x) - (D.x - A.x) * (A.y - B.y);  

	if(fC > 0 && fD > 0) return false;  
	if(fC < 0 && fD < 0) return false;
	return true;  
} 

bool sideIntersectSide(Point A, Point B, Point C, Point D) {  
	if(!lineIntersectSide(A, B, C, D))  
		return false;  
	if(!lineIntersectSide(C, D, A, B))  
		return false;  
	return true;  
}  

int check(int id) {
	// [mi[0], mi[1]], [ma[0], ma[1]]
	// [A, 0], [0, B]
	if(G(A, 0, 0, B, a[id].mi[0], a[id].mi[1])) return 1;
	if(G(A, 0, 0, B, a[id].ma[0], a[id].ma[1])) return 1;
	if(sideIntersectSide(Point(A, 0), Point(0, B), Point(a[id].mi[0], a[id].mi[1]), Point(a[id].ma[0], a[id].ma[1]))) {
		return 1;
	}
	return 0;
}

void ask(int l, int r, int id) {
	if(id == 0) return;
	if(check(id) == 0) return;
	ans = ans + ok(id);
	int mid = (l + r) / 2;
	ask(l, mid - 1, a[id].l);
	ask(mid + 1, r, a[id].r);
}

int main() {
#ifdef ZHOUZHENTAO
	freopen("test.in", "r", stdin);
#endif

	int T;
	scanf("%d", &T);
	while(T --) {
		scanf("%d%d", &n, &q);
		for(int i = 1; i <= n; i ++) {
			scanf("%d%d", &a[i].pos[0], &a[i].pos[1]);
		}
		root = build(1, n, 0);
	/*	
		for(int i = 1; i <= n; i ++) {
		  printf("%d : %d %d mi[0]:%d mi[1]:%d ma[0]:%d ma[1]:%d\n", i, a[i].pos[0], a[i].pos[1], a[i].mi[0], a[i].mi[1], a[i].ma[0], a[i].ma[1]);
		}
		*/
		while(q --) {
			scanf("%d%d", &A, &B);
			ans = 0;
			ask(1, n, root);
			printf("%d\n", ans);
		}
	}

	return 0;
}

 

J. ZOJ 4013 - Super Brain

这个题比较简单,有很多方法判吧。

#include <bits/stdc++.h>
using namespace std;

int T, n;


int main() {
  scanf("%d", &T);
  while(T--) {
    scanf("%d", &n);
    set<int> st;
    for(int i = 1; i <= n; i ++) {
      int x;
      scanf("%d", &x);
      st.insert(x);
    }
    
    int ans;
    for(int i = 1; i <= n; i ++) {
      int x;
      scanf("%d", &x);
      if(st.count(x) > 0) ans = x;
    }
    printf("%d\n", ans);
  }
  return 0;
}

 

posted @ 2018-04-11 08:00  Fighting_Heart  阅读(830)  评论(0编辑  收藏  举报