业余ACMer笔记·6月第三周补题

P2345 [USACO04OPEN]MooFest G

原题地址
在这里插入图片描述
代码:
突然发现洛谷试炼场换成题单了
具体思路并不难,先按Vi从小到大排序(自己做的时候按X排序了),然后维护两个树状数组,一个维护数量前缀和,一个维护坐标前缀和。
代码来自洛谷题解:

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
long long wz[20010],yy[20010],n,mn=20000;
long long ans;
long long read()
{	
    long long xx=0;
    char ch=getchar();
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9')
    {
     	xx=xx*10+ch-'0';
     	ch=getchar();
    }
    return xx;
}
struct node{
    long long xi;
    long long vi;
}a[20010];
bool cmp(node x,node y)
{
   	return x.vi<y.vi;
}
int lobit(int x) {	return x&(-x);}
void crwz(int x) { for(;x<=mn;x+=lobit(x)) wz[x]++;}
int z(int x)//数量前缀和
{
    int sum=0;
    for(;x>=1;x-=lobit(x)) sum+=wz[x];
    return sum;
}
void cryy(int x,int v) { for(;x<=mn;x+=lobit(x)) yy[x]+=v;}
int y(int x)//坐标值前缀和
{
    int sum=0;
    for(;x>=1;x-=lobit(x)) sum+=yy[x];
    return sum;
}
int jdz(int x)//绝对值
{
    if(x<0) return -x;
    else return x;
}
int main()
{
    n=read();
    for(int i=1;i<=n;++i) a[i].vi=read(),a[i].xi=read();
    sort(a+1,a+1+n,cmp);//排序
    for(int i=1;i<=n;++i)
    {
        int j=a[i].xi;
        ans+=a[i].vi*(z(j-1)*j-y(j-1)+y(mn)-y(j)-(z(mn)-z(j))*j);
        crwz(a[i].xi);
        cryy(j,a[i].xi);
    }
    printf("%lld",ans);
    return 0;
}

gym 100801G.Graph

原题地址
在这里插入图片描述
题目大意:给出 n 个点 m 条边的 DAG,要求最多加 k 条有向边(不能形成环),使得可能的字典序最小的拓扑序列最大。输出最终最小的拓扑序列,以及加边数,加的边(1≤n≤1105, 0≤m≤ 1105)

代码:
参考博客
这题主要两个难点,一个是控制加上k条边之后字典序最小的拓扑序列最大,一个是控制不能成环。
参考博客的代码中,maxQ每加如一个元素,k–才执行一次,这样可以控制加边的数目。而每release一个点,才把该点的后继点压入队列,这样可以控制后继点永远不可能有指向该点的边(拓扑序列成环的情况是后继点有一条指向前继点的边)
用操作系统的知识来说,maxQ就是出于阻塞态的进程,minQ就是就绪态的进程,而执行完一个进程后,入度为0的子进程进入就绪态,但可以根据需要把就绪态的进程阻塞。
代码来自参考博客

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define fi first
#define se second
#define dbg(...) cerr<<"["<<#__VA_ARGS__":"<<(__VA_ARGS__)<<"]"<<endl;
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const int inf=0x3fffffff;
const ll mod=1000000007;
const int maxn=1e5+10;
int head[maxn];
struct edge
{
    int to,next;
}e[maxn*2];   //
int tol=0;
void add(int u,int v)
{
    e[++tol].to=v,e[tol].next=head[u],head[u]=tol;
}
priority_queue<int,vector<int>,greater<int> >minQ;
priority_queue<int> maxQ;
int d[maxn];
int ans[maxn];
vector<PII> res;
void release(int u)//释放该点的后继点
{
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(--d[v]==0) minQ.push(v);
    }
}

int main()
{
    freopen("graph.in","r",stdin);
    freopen("graph.out","w",stdout);
    int n,m,k;
    scanf("%d%d%d",&n,&m,&k);
    rep(i,1,m+1)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
        d[v]++;
    }
    rep(i,1,n+1) if(!d[i]) minQ.push(i);
    int len=0;
    while(len<n)
    {
        len++;
        while(k&&minQ.size()>1)
        {
            k--;
            int t=minQ.top();
            minQ.pop();
            maxQ.push(t);
        }
        if(minQ.size()==0)//实在没有可交换的点了,只好把maxQ的队头放出来
        {
            int t=maxQ.top();
            maxQ.pop();
            ans[len]=t;
            release(t);
            res.pb(make_pair(ans[len-1],t));//用ans[len-1]连,可以确保拓扑序列中t在len的位置
        }
        else if(minQ.size()==1&&k&&maxQ.size()&&minQ.top()<maxQ.top())//交换
        {
            k--;
            int t=maxQ.top();
            maxQ.pop();
            maxQ.push(minQ.top());
            minQ.pop();
            ans[len]=t;
            release(t);
            res.pb(make_pair(ans[len-1],t));
        }
        else//否则就执行minQ
        {
            int t=minQ.top();
            minQ.pop();
            ans[len]=t;
            release(t);
        }
    }
    rep(i,1,n+1) printf("%d ",ans[i]);
    puts("");
    printf("%d\n",(int)res.size());
    for(auto it:res)
        printf("%d %d\n",it.fi,it.se);
    return 0;
}

Gym - 100513K Treeland

参考博客

根据各点之间的距离关系构造一棵树,很有意思的一道思维题。运用了树上的节点只有一个父节点且可以有多个子节点、父节点距离根的距离比子节点近的特点。

Gym - 100513G

参考博客

算是一道思维题,有意思的地方在于时间复杂度的计算。

POJ 1177扫描线模板题

原题地址
题意很简单,就是求矩形覆盖区的周长。
参考博客
感觉洛谷的最高赞题解已经讲的很详细了,我加一些代码细节的注释
代码来自参考博客

#include <iostream>
#include <stdio.h>
#include <algorithm>
#define lson (x << 1)
#define rson (x << 1 | 1)
using namespace std;
const int MAXN = 2e4;
int n, X[MAXN << 1];
int x1, y1, x2, y2, pre = 0; /* 先初始化为 0 */

struct ScanLine {
	int l, r, h, mark;
	if(h == rhs.h)
		return mark > rhs.mark;
    return h < rhs.h;
//		注意!这里是后来被 hack 掉以后加上去的
//		在此感谢 @leprechaun_kdl 指出问题
//		如果出现了两条高度相同的扫描线,也就是两矩形相邻
//		那么需要先扫底边再扫顶边,否则就会多算这条边
//		这个对面积并无影响但对周长并有影响
//		hack 数据:2 0 0 4 4 0 4 4 8 输出应为:24
} line[MAXN];

struct SegTree {
	int l, r, sum, len, c;
//  c表示区间线段条数
//sum维护的是横线被多少矩形覆盖
    bool lc, rc;
//  lc, rc分别表示左、右端点是否被覆盖
//  统计线段条数(tree[x].c)会用到
} tree[MAXN << 2];

void build_tree(int x, int l, int r) {
	tree[x].l = l, tree[x].r = r;
	tree[x].lc = tree[x].rc = false;
	tree[x].sum = tree[x].len = 0;
	tree[x].c = 0;
	if(l == r)
		return;
	int mid = (l + r) >> 1;
	build_tree(lson, l, mid);
	build_tree(rson, mid + 1, r);
}

void pushup(int x) {
	int l = tree[x].l, r = tree[x].r;
	if(tree[x].sum) {
	//sum!=0表示该段被完全覆盖
		tree[x].len = X[r + 1] - X[l];
		tree[x].lc = tree[x].rc = true;
		tree[x].c = 1;
//      做好相应的标记
	}
	else {
		tree[x].len = tree[lson].len + tree[rson].len;
		tree[x].lc = tree[lson].lc, tree[x].rc = tree[rson].rc;
		tree[x].c = tree[lson].c + tree[rson].c;
//      如果左儿子左端点被覆盖,那么自己的左端点也肯定被覆盖;右儿子同理
		if(tree[lson].rc && tree[rson].lc)
			tree[x].c -= 1;
//      如果做儿子右端点和右儿子左端点都被覆盖,
//      那么中间就是连续的一段,所以要 -= 1
	}
}
//目前遇到的扫描线题目貌似都没有pushdown函数
void edit_tree(int x, int L, int R, int c) {
//维护的是[ X[tree[x].l],X[tree[x].r+1)这段的状态
	int l = tree[x].l, r = tree[x].r;
	if(X[l] >= R || X[r + 1] <= L)
		return;
	if(L <= X[l] && X[r + 1] <= R) {
		tree[x].sum += c;
		pushup(x);
//pushup也可以拆边,这里不能省略
		return;
	}
	edit_tree(lson, L, R, c);
	edit_tree(rson, L, R, c);
	pushup(x);
}

ScanLine make_line(int l, int r, int h, int mark) {
	ScanLine res;
	res.l = l, res.r = r,
	res.h = h, res.mark = mark;
	return res;
}
//  POJ 不这样做就会CE,很难受

int main() 
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) {
		scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
		line[i * 2 - 1] = make_line(x1, x2, y1, 1);
		line[i * 2] = make_line(x1, x2, y2, -1);
		X[i * 2 - 1] = x1, X[i * 2] = x2;
	}
	n <<= 1;
	sort(line + 1, line + n + 1);
	sort(X + 1, X + n + 1);
	int tot = unique(X + 1, X + n + 1) - X - 1;
	build_tree(1, 1, tot - 1);//注意这里x[l],x[r]管理的是[x[l],x[r+1])这一区间的情况,所以这里是tot-1
	int res = 0;
	for(int i = 1; i < n; i++) {
		edit_tree(1, line[i].l, line[i].r, line[i].mark);
		res += abs(pre - tree[1].len);
//横边的长度是每次扫描线被覆盖线段的长度变化
		pre = tree[1].len;
//      统计横边
		res += 2 * tree[1].c * (line[i + 1].h - line[i].h);
//tree.c维护的是该区间内被覆盖线段的个数,故要*2
//      统计纵边
	}
	res += line[n].r - line[n].l;
//  特判一下枚举不到的最后一条扫描线
	printf("%d", res);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值