您的位置:首页 > 其它

【枚举优化/离散化/并查集】染色问题

2012-08-15 21:59 507 查看
2.染色问题(color.pas/c/cpp)
【题目描述】

平面上有n个珠子排成一排,每个珠子初始颜色为0,你要对他们进行m次染色,每次你选定l和r,然后把[l,r]之间的珠子染成编号c的颜色,每个珠子的最终颜色为它曾经染过的编号最大的颜色,请你写个程序统计每个珠子最终的颜色。

【输入格式】

第一行两个数n,m,表示珠子个数和染色的次数

接下来m行,每行三个数l,r,c如题意所示

【输出格式】

由于数据较大,为了减少输出所用的不必要的时间,请采取以下方法输出:

假如a[i]为第i个珠子的最终颜色

ans := 0;

for i := 1 to n do ans := (ans * 1200007 + a[i]) mod 999911659;

writeln(ans);

注意用int64保存相关变量,防止运算过程中越界

【样例输入】

3 2

1 2 1

2 2 2

【样例输出】

146411103

 

【数据范围】

30% n,m<=5000

50% n,m <= 10000

80% n,m <= 500000

100% n <= 1000000, m <= 2000000

【时间限制】

2s

 

这道题很好,方法有四:(线段树的方法就算了,不在noip要求范围内)

方法一:AC。O(n*m)

这道题我还做得不错,AC掉了。

但是,主要在于我用的是朴素,他们都弱爆了,搞半天都超时。。

主要思路是枚举i从1到n,再枚举编号最小的左右边界能覆盖到i的j区间。

用了一个预处理优化,lmin[i]记录左边界能覆盖到i的编号最小的区间。

rmin[i]记录右边界能覆盖到i得编号最小区间。lmax和rmax同理。

每次枚举就从max(lmin[i],rmin[i])到min(lmax[i],rmax[i])。这样就AC了。。= =、比较弱智、、但是有效。

#include <cstdio>
#include <algorithm>
using std::max;
using std::min;

struct JEW
{
long l;
long r;
long c;
bool operator<(const JEW& j2)const
{
return c < j2.c;
}
};
JEW jew[2000010];
long lmax[1000010];
long lmin[1000010];
long rmax[1000010];
long rmin[1000010];

long getint()
{
long rs=0;bool sgn=1;char tmp;
do tmp = getchar();
while (!isdigit(tmp)&&tmp-'-');
if (tmp=='-'){tmp=getchar();sgn=0;}
do rs=(rs<<1)+(rs<<3)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}

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

long n = getint();
long m = getint();
for (long i=1;i<m+1;i++)
{
jew[i].l = getint();
jew[i].r = getint();
jew[i].c = getint();
}
std::sort(jew+1,jew+m+1);

memset(lmin,0x7f,sizeof lmin);
memset(rmin,0x7f,sizeof rmin);

for (long i=m;i>0;i--)
{
lmax[jew[i].l] = max(lmax[jew[i].l],i);
lmin[jew[i].l] = min(lmin[jew[i].l],i);
rmax[jew[i].r] = max(rmax[jew[i].r],i);
rmin[jew[i].r] = min(rmin[jew[i].r],i);
}
for (long i=1;i<n+1;i++)
{
lmin[i] = min(lmin[i],lmin[i-1]);
lmax[i] = max(lmax[i],lmax[i-1]);
}
for (long i=n;i>0;i--)
{
rmin[i] = min(rmin[i],rmin[i+1]);
rmax[i] = max(rmax[i],rmax[i+1]);
}
long long ans = 0ll;
for (long i=1;i<n+1;i++)
{
bool ok = false;
for (long j=min(lmax[i],rmax[i]);j>=max(lmin[i],rmin[i]);j--)
{
if (jew[j].l<=i && jew[j].r>=i)
{
ok = true;
ans = (ans*1200007ll+jew[j].c)%999911659ll;
break;
}
}
if (!ok)
{
ans = (ans*1200007ll)%999911659ll;
}
}
printf("%ld",ans);
return 0;
}

 

方法二:TLE O(nlgn)

离散化

这个方法是Laury提出来的,我帮他实现了,理论复杂度是能够过的,但是由于其中的一个缺陷而速度大降(由gprof测试得到),也就是从堆中删除已经使用过的颜色太慢,暂时没有找到更好的解决办法。

主要思路是离散化+堆。堆按照颜色编号排序。

 

离散化因为范围过大,我用链表实现,把区间转化为左闭右开,然后在区间的位置处插入区间边界的信息(一般来说右边界优先(但是相反的要后加入,因为链表的特殊插入方式))。

从1到n枚举,遇到左区间,就将颜色入堆(注意不能用栈,例如这种情况([)],如果用栈的话,并不能括号匹配)

遇到右区间,就将该区间的编号标为已访问。

不管有没有遇到边界,都从堆顶不断取出区间的编号,直到这个区间没有被访问过。如果堆为空,则颜色为0,否则颜色为堆顶对应的区间的颜色。

 

血的教训,堆是按照颜色排序的。。因此。。如果说颜色重复。。!!所以我用了set就死了。。只能用Mulitiset!!

 

#include <algorithm>
#include <cstdio>
#include <string>
long l[2000010];
long r[2000010];
long c[2000010];

bool used[2000010];

struct node
{
long i;
node* nxt;
node() {}
node(long i, node* nxt): i(i), nxt(nxt) {}
};
node* head[1000010];

node memory[4000010];
node* ptr = memory;

inline void insert(long a,long c)
{
head[a] = new (ptr++) node(c, head[a]);
}

inline long getint()
{
long rs=0;bool sgn=1;char tmp;
do tmp = getchar();
while (!isdigit(tmp)&&tmp-'-');
if (tmp=='-'){tmp=getchar();sgn=0;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}

long size = 0;
long heap[8000000];

inline void swap(long a,long b)
{
std::swap(heap[a], heap[b]);
}

inline void adjust_down(long l)
{
while ((l<<=1)<size+1)
{
if (l<size&&c[heap[l]]<c[heap[l+1]])
l ++;
if (c[heap[l]]>c[heap[l>>1]])
swap(l,l>>1);
else break;
}
}

inline void adjust_up(long l)
{
while (l > 1)
{
if (c[heap[l]]>c[heap[l>>1]])
swap(l,l>>1);
else break;
l >>= 1;
}
}
inline void push(long a)
{
heap[++size] = a;
adjust_up(size);
}

inline void pop()
{
heap[1] = heap[size--];
adjust_down(1);
}

inline long top()
{
return heap[1];
}

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

long n = getint();
long m = getint();
for (long i=1;i<m+1;i++)
{
l[i] = getint();
r[i] = getint();
c[i] = getint();
insert(r[i]+1,-i);
insert(l[i],i);
}
long long ans = 0;
for (long i=1;i<n+1;i++)
{
for (node* vv=head[i];vv;vv=vv->nxt)
{
long v = std::abs(vv->i);
bool f = vv->i > 0;

if (f)
push(v);//Modify the compare program
else
used[v] = true;
}
while (size>0&&used[top()])
pop();

if (size == 0)
ans = (ans*1200007ll)%999911659ll;
else
ans = (ans*1200007ll+c[top()])%999911659ll;
}
printf("%ld",ans);
return 0;
}


方法三:AC。并查集。O(nm),最优O(n)。

这是最快的方法。

每次涂了一个点,就把它合并到区间的右边界(开区间),涂下一个点时,就更新为getroot(u+1)。

需要注意的是,初始化u = getroot(getint())而不是 u = getint();因为可能一开始就在涂过的地方了。

if (getroot(1) == n+1) break这是一个行之有效的优化。

 

#include <cstdio>
#include <algorithm>
using std::sort;

long fa[1000010];
long color[1000010];

long getroot(long u)
{
if (fa[u] == u)
return u;
return fa[u] = getroot(fa[u]);
}

void merge(long a,long b)
{
fa[getroot(a)] = getroot(b);
}

struct Rag
{
long l;
long r;
long c;
bool operator<(const Rag& rag2)const
{
return c > rag2.c;
}
}range[2000010];

long getint()
{
long rs=0;bool sgn=1;char tmp;
do tmp=getchar();
while (!isdigit(tmp)&&tmp-'-');
if (tmp=='-'){tmp=getchar();sgn=1;}
do rs=(rs<<3)+(rs<<1)+tmp-'0';
while (isdigit(tmp=getchar()));
return sgn?rs:-rs;
}

int main()
{
freopen("color.in","r",stdin);
freopen("color.out","w",stdout);
long n = getint();
long m = getint();

for (long i=1;i<n+1;i++)
fa[i] = i;
fa[n+1] = n+1;

for (long l=1;l<m+1;l++)
{
range[l].l = getint();
range[l].r = getint();
range[l].c = getint();
}
sort(range+1,range+1+m);

for (long l=1;l<m+1;l++)
{
long u = getroot(range[l].l);
while (u < range[l].r+1)
{
color[u] = range[l].c;
merge(u,range[l].r+1);
u = getroot(u+1);
}
if (getroot(1) == n+1)
break;
}
long long ans = 0;
for (long i=1;i<n+1;i++)
{
ans = (ans * 1200007 + color[i]) % 999911659;
}
long out = ans;
printf("%ld",out);
return 0;
}


 

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  优化 insert c struct up 测试