初赛第一场
题目1 : 彩色的树
-
2 3 1 2 2 3 3 1 2 2 1 1 5 1 2 2 3 2 4 2 5 4 1 2 2 1 2 3 2 1
样例输出
-
Case #1: 1 3 Case #2: 1 5
描述
给定一棵n个节点的树,节点编号为1, 2, …, n。树中有n - 1条边,任意两个节点间恰好有一条路径。这是一棵彩色的树,每个节点恰好可以染一种颜色。初始时,所有节点的颜色都为0。现在需要实现两种操作:
1. 改变节点x的颜色为y;
2. 询问整棵树被划分成了多少棵颜色相同的子树。即每棵子树内的节点颜色都相同,而相邻子树的颜色不同。
输入
第一行一个整数T,表示数据组数,以下是T组数据。
每组数据第一行是n,表示树的节点个数。接下来n - 1行每行两个数i和j,表示节点i和j间有一条边。接下来是一个数q,表示操作数。之后q行,每行表示以下两种操作之一:
1. 若为"1",则询问划分的子树个数。
2. 若为"2 x y",则将节点x的颜色改为y。
输出
每组数据的第一行为"Case #X:",X为测试数据编号,从1开始。
接下来的每一行,对于每一个询问,输出一个整数,为划分成的子树个数。
数据范围
1 ≤ T ≤ 20
0 ≤ y ≤ 100000
小数据
1 ≤ n, q ≤ 5000
大数据
1 ≤ n, q ≤ 100000
样例输入
解答 :
然后就好办了,每次修改一个节点,查看 1 它的子节点是否会和自己发生合并/分裂 2 父节点是否会和自己及兄弟节点发生合并/分裂。更新子数个数。
题目2 : 建造金字塔
-
3 2 2 2 3 6 2 5 3 1 1 1 2 2 3 3 3 5 3 1 1 1 2 2 3 3 3 6
样例输出
-
Case #1: 1.00 Case #2: 0.00 Case #3: 1.00
描述
在二次元中,金字塔是一个底边在x轴上的等腰直角三角形。
你是二次元世界的一个建筑承包商。现在有N个建造订单,每个订单有一个收益w,即建造此金字塔可获得w的收益。对每个订单可以选择建造或不建造。
建造一个金字塔的成本是金字塔的面积,如果两个或多个金字塔有重叠面积,则建造这些金字塔时重叠部份仅需建造一次。
建造一组金字塔的总利润是收益总和扣除成本。现给出这些订单,请求出最大利润。
输入
输入数据第一行为一个整数T,表示数据组数。
每组数据第一行为一个整数N,表示订单数目。
接下来N行,每行三个整数x, y, w,表示一个订单。(x, y)表示建造出的金字塔的顶点,w表示收益。
输出
对于每组数据输出一行"Case #X: Y",X表示数据编号(从1开始),Y表示最大利润,四舍五入到小数点后两位。
数据范围
1 ≤ T ≤ 20
0 ≤ w ≤ 107
小数据
1 ≤ N ≤ 20
0 ≤ x, y ≤ 20
大数据
1 ≤ N ≤ 1000
0 ≤ x, y ≤ 1000
解答:
#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
using namespace std;
#define long long int LL
struct tt{
int l, r, w;
friend bool operator<(const tt&a, const tt&b){ return a.l<=b.l; } //@error: not operator (), but <
}ts[1000];
double d[3001];
#define MINN (-0xffffff) //@opt: or (-1e30)
double cost(int x){
return x*x/4.0;//@error: not /4, which return int not double
}
int main(){
int N;
cin>>N;
for(int ii=1; ii<=N; ii++){
int n; cin>>n;
int m = 0;
for(int i = 0; i<n; i++){
int x, y, w; cin>>x>>y>>w;
ts[i] = (tt){x-y+1000, x+y+1000, w};
m = max(m, x+y+1000);
}
sort(ts, ts+n);
for(int i = 0; i<=m; i++) d[i] = MINN; d[0] = 0;
for(int i = 0; i<n; i++){
for(int j = m; j>=0; j--){
if(d[j]==MINN) continue; //@opt: can be removed
if(j>ts[i].r){
d[j] = max(d[j], d[j] + ts[i].w);
}
else{
int l = ts[i].l, r = ts[i].r;
if(l <= j)
d[r] = max(d[r], d[j]+ts[i].w-cost(r-l)+cost(j-l));
else d[r] = max(d[r], d[j]+ts[i].w-cost(r-l));
}
}
}
double mm = 0;
for(int i = 0; i<=m; i++) mm = max(mm, d[i]);
printf("Case #%d: %.2f\n", ii, mm);//@opt: %.2lf
}
return 0;
}
题目3 : 质数相关
-
3 5 2 4 8 16 32 5 2 3 4 6 9 3 1 2 3
样例输出
-
Case #1: 3 Case #2: 3 Case #3: 2
描述
两个数a和 b (a<b)被称为质数相关,是指a × p = b,这里p是一个质数。一个集合S被称为质数相关,是指S中存在两个质数相关的数,否则称S为质数无关。如{2, 8, 17}质数无关,但{2, 8, 16}, {3, 6}质数相关。现在给定一个集合S,问S的所有质数无关子集中,最大的子集的大小。
输入
第一行为一个数T,为数据组数。之后每组数据包含两行。
第一行为N,为集合S的大小。第二行为N个整数,表示集合内的数。
输出
对于每组数据输出一行,形如"Case #X: Y"。X为数据编号,从1开始,Y为最大的子集的大小。
数据范围
1 ≤ T ≤ 20
集合S内的数两两不同且范围在1到500000之间。
小数据
1 ≤ N ≤ 15
大数据
1 ≤ N ≤ 1000
解答:
这是一个二分图。通过最大流找最小点集覆盖,从而间接找到最大独立点集。
错误思路一:
建图:最小的数作为节点放在左边。 从小到大开始:凡是与左边任意数互斥的数都放到右边,反之放到左边。所有互斥关系都需要连边。
证明:假设左边有数x,y ,右边有数xp,yq (p,q是质数),显然x、y同在左边,不会互斥。如果xp和yq互斥,则能够推出x和y互斥,显然这是不成立的(如果xp和yq互斥,则xpk = yq, p\q\k是质数,则 case1: x = sq, 则y=spk,这与pqk为质数矛盾; case 2: p=sq,则p=q,则xk = y,这与xy不互斥矛盾; case 3: k=sq,同case2;)。所以右边任意节点之间也不会互斥。所以这是二分图。
接下来,考虑从左边和右边取出多个数,保证取的数的节点之间没有边。要找到最多的数。
正确思路二:
凡是质数因子个数为奇数的放在左边,偶数的放在右边。如 6=2*3,质因子个数为2,放在右边。
证明: 质因子个数同为奇数或同为偶数的两个数不可能互斥,因为互斥的两个数质因子个数相差1。
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
struct bian{
int next,point,f;
}b[1100000];
int t,f[1100],n,prime[510000],w[510000],lim=500000,p[1100],len,dst[1100],x[1100],totpoint,pd[1100],inf=1100;
int check(int k1,int k2){ //k1 k2是否互斥
if (k1>k2) swap(k1,k2);
if (k2%k1==0&&w[k2]==w[k1]+1) return 1; else return 0;
}
void ade(int k1,int k2,int k3) //添加边 k1->k2
{
len++; b[len].point=k2; b[len].next=p[k1]; b[len].f=k3; p[k1]=len;
}
void add(int k1,int k2,int k3)
{
ade(k1,k2,k3); ade(k2,k1,0);
}
bool bfs() // 通过最大流找最小覆盖(dinic:bfs,change)
{
int head=1,now=0,i,j;
memset(dst,0xff,sizeof dst);
memset(pd,0x00,sizeof pd);
x[1]=0; pd[0]=1; dst[0]=0;
while (head>now)
{
now++; i=p[x[now]];
while (i!=-1)
{
j=b[i].point;
if ((b[i].f)&&(!pd[j]))
{
pd[j]=1; dst[j]=dst[x[now]]+1;
if (j==totpoint)
{
return 1;
}
head++; x[head]=j;
}
i=b[i].next;
}
}
return pd[totpoint];
}
int change(int k1,int k2)// (dinic:bfs,change)
{
if ((k1==totpoint)||(k2==0)) return k2;
int num=0,k,i,j;
i=p[k1];
while (i!=-1)
{
j=b[i].point;
if ((b[i].f)&&(dst[k1]+1==dst[j]))
{
k=change(j,min(k2,b[i].f));
k2=k2-k; num+=k;
b[i].f=b[i].f-k; b[i^1].f+=k;
if (k2==0)
{
break;
}
}
i=b[i].next;
}
if (!num)
{
dst[k1]=-1;
}
return num;
}
int dinic(){
int now=0; while (bfs()) now+=change(0,inf); return now;
}
int solve(){
scanf("%d",&n); len=-1;
memset(p,0xff,sizeof p);
for (int i=1;i<=n;i++) scanf("%d",&f[i]);
int ans=0; totpoint=n+1;
for (int i=1;i<=n;i++)
if (w[f[i]]%2==0) add(0,i,1); //左节点:S->i
else add(i,n+1,1); //右节点:i->T
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
if (w[f[i]]%2==0&&w[f[j]]%2&&check(f[i],f[j])) add(i,j,1);// i=>j
return n-dinic(); //最大独立点集 = n - 最小覆盖点集
}
void make(){
w[1]=0; int len=0; // w[i]记录i的质数因子个数
for (int i=2;i<=lim;i++){
if (w[i]==0){
prime[++len]=i; w[i]=1;//i是素数,质因子个数为1
}
for (int j=1;j<=len&&i*prime[j]<=lim;j++)
if (i%prime[j]==0){
w[i*prime[j]]=w[i]+1; break;
}
else w[i*prime[j]]=w[i]+1;
}
}
int main(){
scanf("%d",&t); make();
for (int i=1;i<=t;i++){
printf("Case #%d: %d\n",i,solve());
}
return 0;
}
初赛第二场
题目1 : 扑克牌
-
5 1 TC 2 TC TS 5 2C AD AC JC JH 4 AC KC QC JC 6 AC AD AS JC JD KD
样例输出
-
Case #1: 1 Case #2: 0 Case #3: 48 Case #4: 24 Case #5: 120
描述
一副不含王的扑克牌由52张牌组成,由红桃、黑桃、梅花、方块4组牌组成,每组13张不同的面值。现在给定52张牌中的若干张,请计算将它们排成一列,相邻的牌面值不同的方案数。
牌的表示方法为XY,其中X为面值,为2、3、4、5、6、7、8、9、T、J、Q、K、A中的一个。Y为花色,为S、H、D、C中的一个。如2S、2H、TD等。
输入
第一行为一个整数T,为数据组数。
之后每组数据占一行。这一行首先包含一个整数N,表示给定的牌的张数,接下来N个由空格分隔的字符串,每个字符串长度为2,表示一张牌。每组数据中的扑克牌各不相同。
输出
对于每组数据输出一行,形如"Case #X: Y"。X为数据组数,从1开始。Y为可能的方案数,由于答案可能很大,请输出模264之后的值。
数据范围
1 ≤ T ≤ 20000
小数据
1 ≤ N ≤ 5
大数据
1 ≤ N ≤ 52
解答:
题目2 : 攻城略地
-
2 4 4 2 6 5 3 4 1 2 1 3 2 3 2 4 4 4 4 6 5 3 4 1 2 1 3 2 3 2 4
样例输出
-
Case #1: 7 Case #2: 18
描述
A、B两国间发生战争了,B国要在最短时间内对A国发动攻击。已知A国共有n个城市(城市编号1, 2, …, n),城市间有一些道路相连。每座城市的防御力为w,直接攻下该城的代价是w。若该城市的相邻城市(有道路连接)中有一个已被占领,则攻下该城市的代价为0。
除了占领城市,B国还要摧毁A国的交通系统,因而他们需要破坏至少k条道路。由于道路损毁,攻下所有城市的代价相应会增加。假设B国可以任意选择要摧毁的道路,那么攻下所有城市的最小代价是多少?
输入
第一行一个整数T,表示数据组数,以下是T组数据。
每组数据第一行包含3个整数n, m, k。
第二行是n个整数,分别表示占领城市1, 2, …, n的代价w。
接下来m行每行两个数i, j,表示城市i与城市j间有一条道路。
输出
对于每组数据输出一行,格式为"Case #X: Y"。X表示数据编号(从1开始),Y为答案。
数据范围
1 ≤ T ≤ 30
k ≤ m
0 ≤ w ≤ 108
小数据
1 ≤ n ≤ 1000
0 ≤ m ≤ 5000
大数据
1 ≤ n ≤ 106
0 ≤ m ≤ 106
解答
#include <iostream>
using namespace std;
#include <algorithm>
typedef long long int ll;
#include <queue>
#include <string.h>
#define MAXN 1000001
vector<int> es[MAXN];
int vis[MAXN]; // branch number of vertex
int vs[MAXN]; // price of vertex
int vv[MAXN]; // min vertex of branch
int ov[MAXN]; // price of non-min vertex
void bfs(int u, int t){
queue<int> q;
vis[u] = t, q.push(u);
while(!q.empty()){
int u = q.front(); q.pop();
for(int i = 0; i<es[u].size(); i++){
if(vis[es[u][i]]<0){
int s = es[u][i];
vis[s] = t, q.push(s);
}
}
}
}
int main(){
int N; scanf("%d", &N); //@error: cin will cause time limit exceed, we need to use scanf
for(int ii=1; ii<=N; ii++){
int n, m, k; scanf("%d %d %d", &n, &m, &k);
for(int i = 0; i<n; i++){
es[i].clear();
scanf("%d", &vs[i]);
}
for(int i = 0; i<m; i++){
int a, b; scanf("%d %d", &a, &b); a--, b--;
es[a].push_back(b), es[b].push_back(a);
}
memset(vis, -1, sizeof(int)*n);
int s = 0; // number of branches
for(int i = 0; i<n; i++){
if(vis[i]<0){
bfs(i, s++);
}
}
memset(vv, -1, sizeof(int)*s); // min vertex of a branch
for(int i = 0 ; i<n; i++){
int j = vis[i];
if(vv[j]<0) vv[j] = i;
else if(vs[vv[j]]>vs[i]) vv[j] = i;
}
ll op = 0; //@error: int op will cause overflow!!!( 10^8 + ... > int)
int idx = 0;
for(int i = 0 ; i<n; i++){
if(vv[vis[i]] != i) ov[idx++] = vs[i];
else op += vs[i];
}
int bn = min(k - (m-(n-s)), n-s); //bn is count of new branches. @error: k may be bigger than m, in which case bn = n-s
printf("Case #%d: ", ii);
if(bn>0){
sort(ov, ov+idx);
for(int i = 0; i<bn; i++){
op += ov[i];
}
}
printf("%ld\n", op);
}
return 0;
}
题目3 : 八卦的小冰
-
1 3 2 8 0 1 0 1 2 1 1 3 1 3 1 1 1 2 3 2 2 3 2 3 1 2 3
样例输出
-
Case #1: 1 2 2 3
描述
小冰是个八卦的人,最近她对一个社交网站很感兴趣。
由于小冰是个机器人,所以当然可以很快地弄清楚这个社交网站中用户的信息啦。
她发现这个社交网站中有N个用户,用户和用户之间可以进行互动。小冰根据用户之间互动的次数和内容判断每对用户之间的亲密度。亲密度非负,若大于零表示这两个用户之间是好友关系。由于这个网站是活跃的,所以小冰会不停地更新用户之间的亲密度。
由于隐私保护,小冰无法知道每个用户的确切性别,但是作为一只很聪明的人工智能,小冰可以通过每个用户的行为来猜测性别。当然这种猜测是不准确的,小冰有可能会改变对一个用户的判断。
小冰想知道这个社交网络的八卦度是多少。八卦度的定义是社交网络中所有异性好友之间的亲密度之和。你能帮助她吗?
输入
第一行一个整数T,表示数据组数。接下来是T组数据,每组数据的格式如下:
第一行是三个整数N, M, Q,分别表示用户数、初始的好友对数、操作数。
第二行是N个空格隔开的数,第i个数表示i号用户的性别,用0或1表示。
接下来的M行,每行三个数x, y, z,代表初始状态用户x和用户y之间的亲密度是z。除此之外的用户之间的亲密度初始为0。
接下来是Q行,每行是以下三种操作中的一种:
1. “1 x”:改变用户x的性别
2. “2 x y z”:改变用户x与用户y之间的亲密度为z
3. “3”:询问八卦度
输出
对于每组数据首先输出一行"Case #X:",X为测试数据编号。
接下来对于每一个询问,输出一行包含询问的八卦度。
数据范围
1 ≤ T ≤ 20
1 ≤ x, y ≤ N
0 ≤ z ≤ 100000
小数据
1 ≤ N, M ≤ 100
1 ≤ Q ≤ 1000
大数据
1 ≤ N, M, Q ≤ 100000
解答: