毯子 题解(COCI 2008-2009Final C)

针对N块矩形毯子上的油扩散问题,采用离线处理和分段等差数列的方法,实现对不同时间点被油染到的毯子面积进行高效计算。通过将矩形翻转至右上角简化计算过程。

[题意]

N 块矩形毯子铺在地上。0秒时(0,0)处有一桶油倒了,然后开始流呀流,每秒往八个方向扩散一个单位。注意,这里的坐标描述一个单元格,不表示点。M个询问,每次问一个时间点被油染到的毯子面积(若有毯子重叠,面积也要累加,如一个单位格被三个毯子覆盖,那么被油染到之后就算3个单位面积)。

[思路]

50分:暴力!n*m枚举每个询问时每块毯子被染到的范围.直接把顶点算出.

100分:

     有两个切入点:

1)每块毯子被染的区域增量随时间改变是有规律的:

     可以分成两个部分:

第一部分是一个公差为2的等差数列:如图所示,增量分别是3,5,7块;

第二部分是一个常数列:图中的常数为3.

每个部分都是一个独立的区间.

2)每块毯子染到的面积是独立的,可以叠加.

确定了以上的两点, 就可以联想到刷漆.把每个时间点的状态叠加.

每个时间点的状态由两个变量来表示:b,k.b表示当前时间点的染到的面积增量的值,k表示当前时间点染到的面积增量的公差.

对于每个时间点t对t-1的增量就可以表示为:b+k+b[t](b[t],表示t时间点的面积增量)

优化:

因为油的变化趋势是随原点对称的,所以把所有矩形都翻到右上角去,

可以在刷漆时更简便.

注意:

1. 输入数据有负数,慎用读入挂

2. 在翻折矩形之后,矩形的个数增加了,数组比忘了开大点!!

 

[启发]

1.    对于询问类的问题可以离线做.

2.   随时间增长的变量可以找规律,挖性质.

<span style="font-family:Comic Sans MS;font-size:18px;">#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#include<map>
#define ll long long
using namespace std;
inline void rd(int &res){
    res=0;
    int k=1;char c;
    while(c=getchar(),c<48&&c!='-');
    if(c=='-'){k=-1;c=getchar();}
    do{
        res=(res<<1)+(res<<3)+(c^48);
    }while(c=getchar(),c>=48);
    res*=k;
}
inline void print(ll k){
    if(k==0)return ;
    print(k/10);
    putchar((k%10)^48);
}
inline void sc(ll k){
    print(k);
    if(k==0)putchar('0');
    putchar('\n');
}
const int M=100005;
const int N=1e6+5;
struct node{int x1,y1,x2,y2;}A[M*4],B[M*4];
int n,m;
ll res[N];
struct LAY{ll b,k;};
vector<LAY>brush[N];
void add(int t,ll b,ll k){//b表示常量,k表示增量  
    brush[t].push_back((LAY){b,k});
}
void solve(){
    int i,j,k,tot=0,now=n;
    for(i=1;i<=now;i++){//矩形反转 
        int x1=A[i].x1,x2=A[i].x2,y1=A[i].y1,y2=A[i].y2;
        if(x1>=0&&x2>=0&&y1>=0&&y2>=0){
            B[++tot]=(node){x1,y1,x2,y2};continue;
        }   
        if(x1<0){
            if(x2<0) A[++now]=(node){-x2,y1,-x1,y2};
            else {
                A[++now]=(node){1,y1,-x1,y2};//分成两块
                A[++now]=(node){0,y1,x2,y2};
            }
            continue;
        }
        else if(x1>=0&&x2>=0&&y1<0){
            if(y2<0)B[++tot]=(node){x1,-y2,x2,-y1};
            else {
                B[++tot]=(node){x1,0,x2,y2};
                B[++tot]=(node){x1,1,x2,-y1};
            }
        }
    }
    for(i=1;i<=tot;i++){
        int x1=B[i].x1,x2=B[i].x2,y1=B[i].y1,y2=B[i].y2;
        int t1,t2,t3;
        ll s1,s2;
        t1=max(x1,y1);
        t2=min(x2,y2);
        t3=max(x2,y2);//算出两个部分分别的时间点
        s1=1ll*(min(x2,t1)-max(x1,0)+1)*(min(y2,t1)-max(y1,0)+1);
        if(x2>y2)s2=y2-y1+1;
        else s2=x2-x1+1;
        if(t2<t1)t2=t1;
        add(t1,s1,2);//刷漆:第一部分初始值为s1,公差为2
        add(t2+1,-s1-1ll*(t2-t1+1)*2,-2);//结束时把第一部分增加的值减去
        add(t2+1,s2,0);//常量部分
        add(t3+1,-s2,0);
    }
    ll b=0,plus=0,ans=0;
    for(i=0;i<N;i++){
        b+=plus;//公差
        for(j=0;j<brush[i].size();j++){
            b+=brush[i][j].b;plus+=brush[i][j].k;//刷漆累加
        }
        ans+=b;
        res[i]=ans;
    }
}
int main(){
    rd(n);
    int t;
    for(int i=1;i<=n;i++){
        rd(A[i].x1);rd(A[i].y1);
        rd(A[i].x2);rd(A[i].y2);
    }
    solve();
    rd(m);
    for(int i=1;i<=m;i++){
        rd(t);
        sc(res[t]);
    }
    return 0;
}
20160625离线赛/COCI 2008-2009FinalC


### 洛谷 P3357 题目相关:**COCI2007-2008#5 AVOGADRO 题解分析** 题目要求找出在三组序列中满足特定条件的列,并统计需要删除的列数。通过分析,该问题本质上是一个数据匹配和统计问题,需要利用计数和条件判断来筛选出符合条件的数据。 #### 题意简述 给出三个序列 a、b、c,每个序列包含 n 个整数。如果 a[i] 在 b 或 c 中没有对应的值,则需要删除 a[i] 所在的列,并统计删除的列数。[^3] #### 解题思路 - 首先,用数组或哈希表记录 b 和 c 中每个值出现的次数。 - 遍历 a 序列中的每个元素,判断其是否在 b 或 c 中存在。 - 如果不存在,则删除该列,并更新 b 和 c 的计数器。 - 最终输出删除的列数。 #### 代码实现 以下是完整的实现代码,时间复杂度为 O(n): ```cpp #include <iostream> using namespace std; const int N = 1e5 + 10; int n, a[N], b[N], b2[N], c[N], c2[N], ans; int main() { // 输入 cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; } for (int i = 1; i <= n; i++) { cin >> b[i]; b2[b[i]]++; // 标记b[i]出现的次数 } for (int i = 1; i <= n; i++) { cin >> c[i]; c2[c[i]]++; // 标记c[i]出现的次数 } // 多次遍历确保所有无效列都被删除 for (int k = 1; k <= 3; k++) { for (int i = 1; i <= n; i++) { // 如果a[i]未被删除 if (a[i]) { // 如果b或c中没有a[i] if (b2[a[i]] == 0 || c2[a[i]] == 0) { // 删除该列,并更新b和c的计数 a[i] = 0; b2[b[i]]--; b[i] = 0; c2[c[i]]--; c[i] = 0; ans++; // 删除列数加一 } } } } // 输出结果 cout << ans; return 0; } ``` #### 优化说明 由于题目数据较弱,只需重复遍历 3 次即可确保所有无效列都被删除。在实际比赛中,建议增加遍历次数(如 100 次)以确保正确性。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值