20230816比赛

T1 矩形

Description

现在我们在一个平面上画了n个矩形。每一个矩形的两边都与坐标轴相平行,且矩形定点的坐标均为整数。现我们定义满足如下性质的图形为一个块:

  • 每一个矩形都是一个块;

  • 如果两个块有一段公共的部分,那么这两个块就会形成一个新的块,否则这两个块就是不同的。

  • 示例:
    图1中的矩形形成了两个不同的块。
    图2中的矩形形成了一个块。
    img

任务:
请写一个程序:

  • 从文本文件PRO.IN中读入各个矩形的顶点坐标;

  • 找出这些矩形中不同的块的数目;

  • 把结果输出到文本文件PRO.OUT中。

Input

​ 在输入文件PRO.IN的第一行又一个整数n,1 <= n <=7000,表示矩形的个数。接下来的n行描述矩形的顶点,每个矩形用四个数来描述:左下顶点坐标(x,y)与右上顶点坐标(x,y)。每个矩形的坐标都是不超过10000的非负整数。

Output

输出唯一的一个整数——这些矩形所形成的不同的块的数目。

Sample Input

9
0 3 2 6
4 5 5 7
4 2 6 4
2 0 3 2
5 3 6 4
3 2 5 3
1 4 4 7
0 0 1 4
0 0 4 1

Sample Output

2

Data Constraint

数据规模
对于60%的数据,有N<=80
对于100%的数据如题目。

一眼冰茶几(并查集),枚举两个矩形,如果这两个矩形存在公告部分就可以加入并查集。

问题就是怎么判存在公共部分。

我定义了以下函数:

  • check1 用于检查 a 的 x1 或 x2 在 b 的 x1 到 x2 中间

       +--+
       |b |
    +--+-+|
    | a| ||
    +--+-+|
       |  |
       +--+
    
  • check2 用于检查 a 的 y1 或 y2 在 b 的 y1 到 y2 中间

       +--+
       |a |
    +--+--+--+
    |  |  |  |
    |  +--+b |
    +--------+
    
  • check3 用于检查 a 的 端点 与 b 的 端点 重合

       +--+
       |a |
    +--+--+
    |  |
    | b|
    +--+
    
  • inside 用于检查 a 在 b 的内部,防止check3误判以下情况

    +--+--+
    |  |a |
    |  +--+
    |  b  |
    |     |
    +-----+
    

同时讲一个我在比赛时想到,但忘了函数名,现在查到的一个仅在Windows环境下对拍的方法:

链接:intersectRect 函数 (winuser.h) - Win32 apps | Microsoft Learn

intersectRect 函数 (winuser.h)

IntersectRect 函数计算两个源矩形的交集,并将交集矩形的坐标置于目标矩形中。 如果源矩形不相交,则 (将所有坐标设置为零的空矩形,) 放置在目标矩形中。

语法

BOOL IntersectRect(
  [out] LPRECT     lprcDst,
  [in]  const RECT *lprcSrc1,
  [in]  const RECT *lprcSrc2
);

参数

[out] lprcDst

指向 RECT 结构的指针,用于接收 lprcSrc1lprcSrc2 参数指向的矩形的交集。 此参数不能为 NULL

[in] lprcSrc1

指向包含第一个源矩形的 RECT 结构的指针。

[in] lprcSrc2

指向包含第二个源矩形的 RECT 结构的指针。

返回值

如果矩形相交,则返回值为非零。

如果矩形不相交,则返回值为零。

注解

由于应用程序可以将矩形用于不同目的,因此矩形函数不使用显式度量单位。 相反,所有矩形坐标和维度都以有符号的逻辑值提供。 使用矩形的映射模式和函数确定度量单位。

示例

有关示例,请参阅 使用矩形

注意,如果你调试,记得加上 #include <windows.h> ,同时提交时记得删掉。

#include <cstdio>
#define ll long long
ll n, ans;
ll fa[7010];
bool vis[7010];
struct RECT {
	ll x1, y1, x2, y2;
} a[7010];
ll find(ll x) {
	if(fa[x] == x) return fa[x];
	return fa[x] = find(fa[x]);
}
bool check1(RECT a, RECT b) {
	return (a.x1 <= b.x2 && b.x2 <= a.x2) || (a.x1 <= b.x1 && b.x1 <= a.x2);
}
bool check2(RECT a, RECT b) {
	return (a.y1 <= b.y2 && b.y2 <= a.y2) || (a.y1 <= b.y1 && b.y1 <= a.y2);
}
bool inside(RECT a, RECT b) {
	return (b.x1 <= a.x1 && a.x2 <= b.x2 && b.y1 <= a.y1 && a.x2 <= b.x2);
}
bool check3(RECT a, RECT b) {
	return !(a.x1 == b.x2 && a.y1 == b.y2 && (inside(a, b) || !inside(b, a))) &&
			!(a.x1 == b.x2 && b.y1 == a.y2 && (inside(a, b) || !inside(b, a)));
}
int main() {
	freopen("pro.in", "r", stdin);
	freopen("pro.out", "w", stdout);

	scanf("%lld", &n);
	for(ll i = 1; i <= n; i++) fa[i] = i;
	for(ll i = 1; i <= n; i++) {
		scanf("%lld %lld %lld %lld", &a[i].x1, &a[i].y1, &a[i].x2, &a[i].y2);
	}
	for(ll i = 1; i <= n; i++) {
		for(ll j = i + 1; j <= n; j++) {
			if((check1(a[i], a[j]) || check1(a[j], a[i])) && (check2(a[i], a[j]) || check2(a[j], a[i])) && (check3(a[i], a[j]) && check3(a[j], a[i]))) {
				ll x = find(i);
				ll y = find(j);
				fa[x] = y;
			}
		}
	}
	for(ll i = 1; i <= n; i++) {
		if(!vis[find(i)]) {
			vis[fa[i]] = 1;
			ans++;
		}
	}
	printf("%lld", ans);
}

T2 平坦的折线(lam)

Description

​ 现在我们在一张纸上有一个笛卡尔坐标系。我们考虑在这张纸上用铅笔从左到右画的折线。我们要求任何两个点之间连接的直线段与x轴的夹角在-45~45之间,一条满足以上条件的折线称之为平坦的折线。假定给出了n个不同的整点(坐标为整数的点),最少用几条平坦的折线可以覆盖所有的点?

例子:
img
图中有6个整点:(1,6), (10,8), (1,5), (2,20), (4,4), (6,2),要覆盖它们至少要3条平坦的折线。

任务:
写一个程序:
从文件lam.in中读入点的个数以及它们的坐标。
计算最少需要的折线个数。
将结果写入文件lam.out。

Input

​ 在输入文件lam.in的第一行有一个正整数n,不超过30000,代表点的个数。接下来的n行表示这些点的坐标,每行有两个用一个空格隔开的整数x,y,0 <= x <= 30000, 0 <= y <= 30000。第i+1行的数字代表第i个点的坐标。

Output

​ 在输出文件lam.out的第一行应当有且仅有一个整数——表示覆盖所有的点最少需要的折线数。

Sample Input

6
1 6
10 8
1 5
2 20
4 4
6 2
 

Sample Output

3

Data Constraint

数据规模
对于20%的数据,有N<=15。
对于100%的数据如题目。

比赛时想到可以不用暴力旋转,只需要打一个平衡树(其实当时我想的是我最喜欢的权值线段树)然后找后继就行了,但是处理很麻烦,没打出来。

考虑整个图旋转45°,然后因为题目的要求,我们的折线现在只能严格递增。用f储存折线结尾的y坐标,然后从右到左跑一遍就行了。对于小于当前所有点的y坐标就新开折线,否则二分查找后继(或者平衡树)。

#include <cstdio>
#include <set>
#include <cmath>
#include <climits>
#define ll long long
using namespace std;
ll n;
struct POINT {
    ll x, y;
    friend bool operator> (const POINT &x, const POINT &y) {
        if(x.x == y.x) return x.y < y.y;
        return x.x > y.x;                                   // 注意是从大到小排,具体原因自己想想吧QWQ
    }
} a[30010], g[30010];
void solve(ll l, ll r) {
    if(l == r) return;
    ll mid = (l + r) >> 1;
    solve(l, mid);
    solve(mid + 1, r);

    ll pos1 = l, pos2 = mid + 1;
    for(ll i = l; i <= r; i++) {
        if(pos2 > r || (pos1 <= mid && a[pos1] > a[pos2])) {
            g[i] = a[pos1++];
        } else {
            g[i] = a[pos2++];
        }
    }

    for(ll i = l; i <= r; i++) {
        a[i] = g[i];
    }
}
ll f[30010], ans;

int main() {
	freopen("lam.in", "r", stdin);
	freopen("lam.out", "w", stdout);

    scanf("%lld", &n);
    for(ll i = 1; i <= n; i++) {
        ll x, y;
        scanf("%lld %lld", &x, &y);
        a[i].x = y - x;
        a[i].y = y + x;
    }

    solve(1, n);

    f[0] = INT_MAX;

    for(ll i = 1; i <= n; i++) {
        if(f[ans] > a[i].y) f[++ans] = a[i].y;                  // 要求单调递增,所以这里只能开新链了
        else {
            // 选一个最适合的ans来连接
            ll l = 1, r = ans, res = 1;
            while(l <= r) {
                ll mid = (l + r) >> 1;
                if(f[mid] <= a[i].y) {
                    res = mid;
                    r = mid - 1;
                } else {
                    l = mid + 1;
                }
            }
            f[res] = a[i].y;
        }
    }
    printf("%lld", ans);
}

T3 Idiot 的间谍网络

Description

作为一名高级特工,Idiot 苦心经营多年,终于在敌国建立起一张共有n 名特工的庞大间谍网络。
当然,出于保密性的要求,间谍网络中的每名特工最多只会有一名直接领导。现在,Idiot 希望整理有关历次特别行动的一些信息。
初始时,间谍网络中的所有特工都没有直接领导。之后,共有m 次下列类型的事件按时间顺序依次发生:
• 事件类型1 x y:特工y 成为特工x 的直接领导。数据保证在此之前特工x 没有直接领导;
• 事件类型2 x:特工x 策划了一起特别行动,然后上报其直接领导审批,之后其直接领导再上报其直接领导的直接领导审批,以此类推,直到某个特工审批后不再有直接领导;
• 事件类型3 x y:询问特工x 是否直接策划或审批过第y 次特别行动。所有特别行动按发生时间的顺序从1 开始依次编号。数据保证在询问之前,第y 次特别行动已经发生过。
作为一名高级特工,Idiot 当然不会亲自办事。于是,Idiot 便安排你来完成这个任务。

Input

第一行两个正整数n 和m,分别表示间谍网络中的特工总数,以及事件的总数。
接下来m 行,第i 行给出第i 个事件的信息,格式及含义参见题面。

Output

输出共t 行,其中t 表示询问的总数。第i 行输出”Y ES” 或者”NO”,表示第i 次询问的答案。

Sample Input

6 12
2 1
1 4 1
3 4 1
1 3 4
2 3
3 4 1
2 3
3 4 2
3 1 1
3 1 3
3 1 2
1 2 4

Sample Output

NO
NO
YES
YES
YES
YES

Data Constraint

对于30% 的数据,n <= 3 10^3,m <= 5 10^3;
对于60% 的数据,n <=2 * 10^5,m <= 2 * 10^5;
额外20% 的数据,保证在任意时刻,整张间谍网络由若干条互不相交的链构成;
对于100% 的数据,n <= 5 * 10^5,m <= 5 * 10^5;
C + + 选手的程序在评测时使用编译选项-Wl;--stack = 104857600。

考试时看到这道题想的是离线,然后打了个暴力离线加优化。

结果真的是离线

题目要求我们实现:

  1. 设置 \(x\)\(y\) 的祖先
  2. \(x\) 及其祖先打上标记
  3. 判断在某一时刻 \(x\) 是否有 \(y\) 的标记。

我们稍微转换操作 3. ,可以先把查询储存下来,遍历我们的操作 2. 的行动编号 ,如果刚好有对操作 2. 的查询,那么我们就在进行完操作 2. 后(因为题目保证在询问之前,第 y 次特别行动已经发生过。),查询 \(x\) 是否为行动 \(y\) 的发起者的祖先,这样就可以去掉 某一时刻 这个条件。

那么怎么判断 \(x\)\(y\) 的祖先呢?

说到祖先,我们总可以想到并查集。

那么我们只需要查询两人的祖先即可……吗?

非也,因为可能为这样,在这里,x与y的祖先虽然相同,可是却是y是x的祖先:

graph TB 1((1)) 2((2)) 3((y)) 4((3)) 5((x)) 1 --> 2 1 --> 3 3 --> 4 3 --> 5

那么我们就可以判断两人的深度……吗?

非也,因为可能为这样,在这里,x与y的祖先虽然相同,x与y无关系:

graph TB 1((1)) 2((2)) 3((y)) 4((3)) 5((x)) 1 --> 2 1 --> 3 2 --> 4 2 --> 5

那么应该怎么办呢?这时,时间戳就派上用场了。


当我们在遍历一个节点的子树前,我们先要添加一个 \(st\) 时间戳为 ++ti

当我们在遍历完一个节点的子树后,我们先要添加一个 \(ed\) 时间戳为 ++ti

那么这样,但凡时间戳在 \([st, ed]\) 内的都是这个节点的子树。

所以我们在判祖先相同的同时还要判时间戳在 \([st, ed]\) 内。

#include <cstdio>
#include <algorithm>
#define ll long long
using namespace std;
ll n, m;

/* 离线操作 */
struct QUERY {
	ll op, x, y, id;
} query[500010], e[500010], a[500010], d[500010];
ll ecnt, acnt, dcnt;

/* 冰茶几 */
ll fa[500010];
ll find(ll x) {
	if(fa[x] == x) {
		return x;
	}
	return fa[x] = find(fa[x]);
}
void merge(ll x, ll y) {
	x = find(x);
	y = find(y);
	fa[x] = y;
}



/* 前向星 */
ll head[500010];
ll nxt[500010];
ll to[500010], cnt;
void addEdge(ll u, ll v) {
	++cnt;
	to[cnt] = v;
	nxt[cnt] = head[u];
	head[u] = cnt;
}

/* 记录时间戳 */
ll st[500010], ed[500010];

bool vis[500010];

ll ti;


/* 生成dfn */
void dfs(ll u) {
	ti++;
	vis[u] = true;
	st[u] = ti;
	for(ll i = head[u]; i; i = nxt[i]) if(!vis[to[i]]){
		ll v = to[i];
		dfs(v);
	}
	ed[u] = ti;
}

bool ans[500010];

bool cmp(QUERY x, QUERY y) {
	return x.y < y.y;
}

int main() {
	scanf("%lld %lld", &n, &m);
	for(ll i = 1; i <= n; i++) fa[i] = i;
	for(ll i = 1; i <= m; i++) {
		query[i].id = i;
		scanf("%lld", &query[i].op);
		switch(query[i].op) {
			case 1:
				scanf("%lld %lld", &query[i].x, &query[i].y);
				addEdge(query[i].y, query[i].x);
				merge(query[i].x, query[i].y);
				e[++ecnt] = query[i];
				break;
			case 2:
				scanf("%lld", &query[i].x);
				d[++dcnt] = query[i];
				break;
			case 3:
				scanf("%lld %lld", &query[i].x, &query[i].y);
				a[++acnt] = query[i];
				break;
		}
	}
	for(ll i = 1; i <= n; i++) {
		ll u = find(i);
		if(!vis[u]) {
			dfs(u);
		}
	}
	for(ll i = 1; i <= n; i++) fa[i] = i;									// 清空并查集,防止出现后来的上级误以为审批的情况
	sort(a + 1, a + 1 + acnt, cmp);											// 按照查询的时间顺序进行排序
	ll ei = 1, ai = 1;
	for(ll di = 1; di <= dcnt; di++) {										// 枚举特别行动
		for(; e[ei].id <= d[di].id; ei++) merge(e[ei].x, e[ei].y);			// 把上一次的发起行动,到这一次的发起行动,之间的认爹全部跑一遍
		for(; a[ai].y == di; ai++) {										// 看有没有查询这次发起行动的
			ll u = find(a[ai].x);											// 找父亲
			ll v = find(d[di].x);
			if(u == v &&													// 判断祖先相同,并且 
			st[a[ai].x] <= st[d[di].x] && st[d[di].x] <= ed[a[ai].x]) {		// 这次发起行动在x的子树间
				ans[a[ai].id] = 1;
			}
		}
	}
	
	for(ll i = 1; i <= m; i++) {
		if(query[i].op == 3)
			printf("%s\n", ans[i] ? "YES" : "NO");
	}
}

T4 Idiot 的乘幂

Description

img

Input

第一行一个正整数t,表示测试数据组数。
接下来t 行,每行五个正整数a、b、c、d、p,表示一组测试数据。

Output

一共t 行,第i 行表示第i 组测试数据的答案。若该组测试数据无解,则输出No Solution!,否则输出一个正整数x(1 <= x < p),表示同余方程组的解。

Sample Input

10
22 1 3 17 24
6 12 5 6 13
16 16 9 1 19
11 1 14 2 23
8 4 17 15 19
10 3 9 1 13
11 1 2 2 23
13 2 14 12 17
11 9 7 11 14
15 10 7 7 17

Sample Output

17
2
5
16
14
3
18
No Solution!
11
12

Data Constraint

img

暴力,寄。

然后结束后后知后觉,看题目那么多gcd,那肯定得考虑exgcd呀。

把题目中的方程组组合在一起就变成了:

\[X^a\cdot X^c\equiv X^{a+c}\equiv b \cdot d (\mod p) \]

然后我们假定两个数\(x,y\)使得:

\[ax+cy=1 \]

那么显然:

\[X^{ax+cy}\equiv X(\mod p) \]

\[X^{ax+cy}\equiv X^{ax}\cdot X^{cy}\equiv X^{a^x}\cdot X^{c^y}\equiv b^x\cdot d^y(\mod p) \]

所以

\[X\equiv b^x\cdot d^y(\mod p) \]

那我们就可以根据 \(ax+cy=1\) 跑一遍exgcd,再根据 \(X\equiv b^x\cdot d^y(\mod p)\),就能得出 \(X\) 了。


\(x\) 可能为负数,如果它们为负数,那么它就相当于 \(\frac{1}{b^{-x}}=b^{-1^{-x}}\)

\(y\) 同上。所以如果它们小于0记得还要求个逆元。

#include <cstdio>
#define ll long long
ll t, a, b, c, d, p, x, y;
ll qpow(ll n, ll q) {
    if(q == 0) return 1;
    if(q % 2 == 0) {
        ll res = qpow(n, q / 2);
        return res * res % p;
    }
    return qpow(n, q - 1) * n % p;
} 
ll exgcd(ll a, ll b) {
    if(b == 0) {
        x = 1, y = 0;
        return a;
    }
    ll r = exgcd(b, a % b);
    ll t = x;
    x = y;
    y = t - (a/b) * y;
    return r;
}
int main() {
    scanf("%lld", &t);
    while(t--) {
        scanf("%lld %lld %lld %lld %lld", &a, &b, &c, &d, &p);
        exgcd(a, c);
        ll l, r;
        ll tmpx = x, tmpy = y;
        if(tmpx >= 0) {
            l = qpow(b, tmpx);
        } else {
            exgcd(b, p);
            x = (x % p + p) % p;
            l = qpow(x, -tmpx);
        }
        if(tmpy >= 0) {
            r = qpow(d, tmpy);
        } else {
            exgcd(d, p);
            x = (x % p + p) % p;
            r = qpow(x, -tmpy);
        }
        if(qpow(l * r % p, a) == b % p && qpow(l * r % p, c) == d % p) {
            printf("%lld\n", l * r % p);
        } else {
            printf("No Solution!\n");
        }
    }
}
posted @ 2023-08-16 21:49  ZnPdCo  阅读(27)  评论(0编辑  收藏  举报