2008年11月19日
为了解决这样的问题,我们需要关于一个整型数组A[1..N]的数据结构,它支持这样的操作,起初,数组中的元素都是0,而且这样的数据结构支持下面两种操作:
1.更新A[pos] = A[pos] + value (其中value,pos是任意给定的,且可能为负值)
2.计算sum(A, l, r) = A[l]+A[l+1]+A[l+2]+...+A[r-1]+A[r] (其中l, r是任意给定的)
我们需要这样的操作都在O(log N)的时间内完成。能够实现这样操作的数据结构是 Binary Idexed Trees (BIT)。
在我们的解决方案中我们需要两个树状数组(BIT), 下标从1..L。其中第一个叫做cnt, cnt[i] 是当前种在i这个位置中树的个数(我们增加将所有树的下标增加1,使得所有的下标在1..L内)。第二个数组叫做dst, dst[i] = cnt[i]*i。换句话说,dst[i]是所有种在位置i的树的距离和。
假设我们要在x[i]处种一棵树, 并且想要尽快计算出他的price。我们有CL=sum(cnt, 1, x[i]-1) 棵树在x[i]的左边。每个种在x0处的树距离第i棵树的距离是x[i]-x0。所以左边所有树到第i棵树的距离和就是 CL*x[i]-sum(all x0) = CL*x[i]-sum(dst, 1, x[i]-1)。同样的有CR=sum(cnt, x[i]+1, L)棵树在第i棵树的右边,他们到第i棵树的距离和是 sum(dst, x[i]+1, L)-CR*x[i]。所以总的price 可以由下式计算:

Code
price = x[i] * (sum(cnt, 1, x[i]-1) - sum(cnt, x[i]+1, L)) +
sum(dst, x[i]+1, L) - sum(dst, 1, x[i]-1
计算好price后我们只需要更新cnt, dst数组, cnt[x[i]]=cnt[x[i]]+1, dst[x[i]]=dst[x[i]]+x[i]。

Code
class ProductOfPrices {
public:
int product(int, int, int, int, int);
};
int L;
int64 cnt[200001], dst[200001];
int64 x[200001];
void add(int64 a[], int64 i, int64 x)
{
while (i <= L)
{
a[i] += x;
i |= i-1;
i++;
}
}
int64 getSum(int64 a[], int64 i)
{
int64 res = 0;
while (i > 0)
{
res += a[i];
i &=i-1;
}
return res;
}
int64 getSum(int64 a[], int64 i, int64 j)
{
if (j >= i)
return getSum(a, j) - getSum(a, i-1);
else
return 0;
}
int ProductOfPrices::product(int N, int l, int X0, int A, int B)
{
L = l;
x[0] = X0%L;
for (int i = 1; i < N; i++)
x[i] = ((int64(x[i-1])*int64(A))+int64(B)) % int64(L);
memset(cnt, 0, sizeof(cnt));
memset(dst, 0, sizeof(dst));
add(cnt, x[0]+1, 1);
add(dst, x[0]+1, x[0]+1);
int64 res = 1;
for (int i = 1; i < N; i++)
{
int64 price = (x[i]+1) * getSum(cnt, 1, x[i]) - getSum(dst, 1, x[i])
+ getSum(dst, x[i]+1, L) - getSum(cnt, x[i]+1, L) * (x[i]+1);
price %= 1000000007;
res = (res * price) % 1000000007;
add(cnt, x[i]+1, 1);
add(dst, x[i]+1, x[i]+1);
}
return (int)res;
2008年11月12日

Code
#include <iostream>
#include <memory.h>
using namespace std;
const int maxn = 310;
const int maxe = 2048;
const int QSIZE = 1000000;
const bool db = false;
#define INSERT(x,y,z) p[top].j=y,p[top].value=z,p[top].next=head[x],head[x]=top++
struct data
{
int j, value, next;
};
struct queue
{
int a[QSIZE], head, tail;
queue():head(0), tail(0){}
bool empty(){return head == tail;}
void set_empty(){head=tail=0;}
int front(){return a[head];};
void pop(){head=(head+1)%QSIZE;};
void push(int x){a[tail]=x;tail=(tail+1)%QSIZE;}
}Q;
data p[maxe];
int head[maxn], top;
int Count[maxn], pre[maxn], E[maxn];
int f[maxn];
int V[maxn];
int n, m;
void ini()
{
memset(head, -1, sizeof(head));
top = 0;
}
int cnt = 0;
void Calc(int& a, int& b, int x)
{
int cnt = 0;
a=V[x];b=E[x];
int tmp = pre[x];
while (tmp != x)
{
cnt++;
if (cnt > 200) break;
a += V[tmp];
b += E[tmp];
tmp = pre[tmp];
}
}
bool Bellman_ford(int& a, int& b)
{
Q.set_empty();
Q.push(0);
memset(f, 63, sizeof(f));
memset(pre, -1, sizeof(pre));
memset(E, 0, sizeof(E));
memset(Count, 0, sizeof(0));
Count[0]=1;
f[0]=0;
while (!Q.empty())
{
int cur = Q.front();
Q.pop();
int i;
for (i = head[cur]; i != -1; i = p[i].next)
{
int next = p[i].j;
int next_f = f[cur] + a*p[i].value-b*V[next];
if (next_f < f[next])
{
f[next] = next_f;
E[next] = p[i].value;
Count[next] = Count[cur]+1;
Q.push(next);
if (pre[next] == cur)
{
pre[next] = cur;
Calc(a,b,next);
return true;
}
pre[next] = cur;
}
}
}
return false;
}
int main()
{
int i, j, k;
//freopen("input.txt", "r", stdin);
while (scanf("%d%d", &n, &m) == 2)
{
ini();
for (i = 0; i < n; i++)
scanf("%d", &V[i]);
for (i = 0; i < m; i++)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
x--,y--;
INSERT(x,y,z);
}
int a1 = 0, b1 = 1, a2 = 1, b2 = 0;
int cnt = 0;
while (a1!=a2 || b1!=b2)
{
int a = a1+a2, b = b1+b2;
printf("%d/%d %d/%d\n", a1, b1, a2, b2);
if (!Bellman_ford(a, b)) a2=a,b2=b;
else
a1=a,b1=b;
}
printf("%d %d\n", a1, b1);
}
return 0;
}
2008年10月22日
C/C++ 模式
指定为C++模式的方法:一般根据扩展名自动设定,不用指定,不过有时候你希望.h文件是C++模式的(缺省是C模式),
在文件第一行(或其末尾)上加入
// -- C++ --
语法高亮:不是C模式专有,顺便提一下,M-x global-font-lock-mode RET 或在.emacs中加入(global-font-lock-mode t)。
在console下,Emacs21才支持语法高亮。(语法高亮,不同关键字用不同的颜色来突出)
子模式:
-
auto-state 当你输入时自动缩进,自动换行
-
hungry-state 当你Backspace时,自动删除尽可能多的空白和空行
-
C-c C-t 同时转换(开/关)auto-state和hungry-state子模式
-
C-c C-a 转换 auto-state 子模式
-
C-c C-d 转换 hungry-state 子模式
-
C-c . 设置缩进风格(按TAB键可列出可用的风格,缺省的为gnu,其缩进为2个字符;linux为8个;k&r为5个...)
-
TAB 重新缩进当前行
-
M-/ 自动补齐(缓冲区中能找得到的串)
-
M-; 行尾加入注释
-
C-c C-e 扩展宏
-
C-c C-c 注释掉整个区域
-
C-c C-" 将区域中的每一行结尾都加入一个'"'字符
编译和调试
启动gdb调试器后,光标在源码文件缓冲区中时:
-
C-x SPC 在当前行设置断点
-
C-x C-a C-s step
-
C-x C-a C-n next
-
C-x C-a C-t tbreak
-
C-x C-a C-r continue
2008年9月18日

Code
/*
建立图2*i, 2*i+1是两个对立的
结果中如果color[j] = RED表示取该点
*/
#include <iostream>
#include <queue>
using namespace std;
const bool db = false;
const int MAXN = 2000+10;
const int MAXV = 4000000+10;
#define EMPTY -1
#define RED 0
#define BLUE 1
#define ADD(head, data, next, top, x, y) data[top]=y,next[top]=head[x],head[x]=top,top++
#define FOREACH(head, data, next, x, y) for (int _i=head[x];y=data[_i],_i>0;_i=next[_i])
#define MIN(x, y) x=x<y?x:y
#define CHANGE(x) x&1?x-1:x+1
struct stack{
int top, data[MAXN];
stack(){top=0;}
void push(int x){data[top++]=x;}
int pop(){return data[--top];}
}S;
int dfn[MAXN], low[MAXN], color[MAXN], color2[MAXN],cnt, mark,N,head[MAXN],data[MAXV], next[MAXV],top,nhead[MAXN];
int ndata[MAXV], nnext[MAXV],ntop,shead[MAXN],sdata[MAXN], snext[MAXN],stop,in_degree[MAXN],ncolor[MAXN];
int DFS(int x){
dfn[x]=low[x]=mark++;int y; S.push(x);
FOREACH(head, data, next, x, y){if(dfn[y] == -1)DFS(y);
if (color[y] == -1) MIN(low[x], dfn[y]),MIN(low[x], low[y]);
}
if (dfn[x]==low[x]) {
int tmp;do tmp = S.pop(),color[tmp] = cnt;while (tmp != x);cnt++;
}return 1;
}
void Gabow(){
memset(dfn, -1, sizeof(dfn));memset(color, -1, sizeof(color));
cnt = 0,mark = 0; for (int i = 0; (i<N&&dfn[i]==-1)&&DFS(i), i < N; i++);
}
void ColorToBlue(int x){
int y;color2[x] = BLUE; FOREACH(nhead, ndata, nnext, x, y)
if (color2[y] == EMPTY) ColorToBlue(y);
}
bool TwoSat(){
Gabow();
for (int i = 0; i+1 < N; i+=2) if (color[i] == color[i+1]) return false;
memset(in_degree, 0, sizeof(in_degree));memset(color2, EMPTY, sizeof(color2));
stop = 1;memset(shead, -1, sizeof(shead));
for (int i = 0; i < N; i++) ADD(shead, sdata, snext, stop, color[i], i);
ntop = 1;memset(nhead, -1, sizeof(nhead));
for (int i = 0; i < N; i++) {int x;
FOREACH(head, data,next, i, x) if (color[i] != color[x])
ADD(nhead, ndata, nnext, ntop, color[x], color[i]),in_degree[color[i]]++;
}queue<int> Q;
for (int i = 0; i < cnt; i++)if (in_degree[i] == 0) Q.push(i);
while (!Q.empty()){int x = Q.front();Q.pop();
if (color2[x] != EMPTY) continue;color2[x] = RED;int y;
FOREACH(nhead, ndata, nnext, x, y){in_degree[y]--; if (in_degree[y] == 0)Q.push(y);
}int tmp;
FOREACH(shead, sdata, snext, x, y) if (color2[ color[ tmp = CHANGE(y)] ] == EMPTY)
ColorToBlue(color[ CHANGE(y) ]);
}memset(color, RED, sizeof(color));
for (int i = 0; i < cnt; i++)if (color2[i] == BLUE){int y;
FOREACH(shead, sdata, snext, i, y) color[y] = BLUE;} return true;
}
2008年9月15日
GCJ Practice Contest Problem C:
In this problem, u r asked to calculate the number of all the Hamiltonian Circles in complete graph only with at most 15 forbidden edges.
We know, finding the Hamiltonian Circles is a NP problem, how can we count all the Hamiltonian Circles in a graph with so many edges?
First, let us think about this problem. Give u a complete graph with N vertexes , can u tell me how many Hamiltonian Circles exist in it?
We can easily find that number will be (N-1)! / 2.
And back to the original problem, we find that the forbidden edges are very small. So we come up with an idea, we can use the number of Hamiltonian Circles in a complete graph minus those circles using forbidden edges.

Code
#include <iostream>
#include <vector>
using namespace std;
const int MOD = 9901;
const bool DB = false;
struct Edge
{
int s, t;
};
vector<int> edge[310];
Edge forb[16];
int N, K;
bool vis[310];
int fact[310];
int DFS(int now, int parent)
{
for (int i = 0; i < edge[now].size(); ++i)
if (!vis[ edge[now][i] ] && edge[now][i] != parent)
{
vis[ edge[now][i] ] = true;
return DFS(edge[now][i], now) + 1;
}
return 1;
}
int Solve()
{
memset(vis, false, sizeof(vis));
for (int i = 0; i < N; i++)
if (edge[i].size() > 2) return 0;
int comp = 0;
for (int i = 0; i < N; i++)
if (edge[i].size() == 1 && !vis[i])
{
vis[i] = true;
DFS(i, -1);
++comp;
}
int zeros = 0;
for (int i = 0; i < N; ++i)
if (edge[i].size() == 0)
++zeros;
for (int i = 0; i < N; ++i)
{
if (edge[i].size() == 2 && !vis[i])
{
int depth = 0;
// vis[i] = true;
depth = DFS(i, -1);
if (DB) printf("depth = %d\n", depth);
if (depth == N+1) return 1;
else
{
if (DB)
printf("exit when i = %d\n", i);
return 0;
}
}
}
if (DB)
printf("zeros = %d, comp = %d\n", zeros, comp);
int totn = zeros + comp;
if (comp) return fact[totn - 1] * (1LL << (comp-1)) % MOD;
else
return (fact[totn - 1] / 2) % MOD;
}
int main()
{
int t;
freopen("C-large-practice.in", "r", stdin);
freopen("C-large_parctice.out", "w", stdout);
fact[1] = 1;fact[0] = 1;
for (int i = 2; i <= 301; i++)
fact[i] = fact[i-1] * i % MOD;
scanf("%d", &t);
for (int ctr = 1; ctr <= t; ctr++)
{
scanf("%d%d", &N, &K);
for (int i = 0; i < K; i++)
{
scanf("%d%d", &forb[i].s, &forb[i].t);
forb[i].s--, forb[i].t--;
}
int ans = 0, ret;
for (int i = 0; i < (1<<K); i++)
{
if (DB)
printf("*****************\n");
for (int j = 0; j < N; j++)
edge[j].clear();
int mult = 1;
for (int j = 0; j < K; j++)
if (i&(1<<j))
edge[forb[j].s].push_back(forb[j].t), edge[forb[j].t].push_back(forb[j].s), mult *= -1;
ret = Solve();
if (DB)
printf("with mask %d, ret = %d, mult = %d\n", i, ret, mult);
ans = (ans + mult * ret) % MOD;
if (DB)
printf("ans = %d\n", ans);
}
printf("Case #%d: %d\n", ctr, ans < 0 ? ans + MOD : ans );
}
return 0;
}
2008年9月11日
约瑟夫问题的数学方法
无论是用链表实现还是用数组实现都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较烦,而且时间复杂
度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。
为了讨论方便,先把问题稍微改变一下,并不影响原意:
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号
。
我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开
始):
k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2
并且从k开始报0。
现在我们把他们的编号做一下转换:
k --> 0
k+1 --> 1
k+2 --> 2
...
...
k-2 --> n-2
k-1 --> n-1
变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x是最终的胜利者,那么根
据上面这个表把这个x变回去不刚好就是n个人情况的解吗?!!变回去的公式很简单,相信大家都可以推出来:x'
=(x+k)%n
如何知道(n-1)个人报数的问题的解?对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢?当然是先求(n-3)的
情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:
令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]
递推公式
f[1]=0;
f[i]=(f[i-1]+m)%i; (i>1)
有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。因为实际生活中编号总是从1开始,
我们输出f[n]+1
由于是逐级递推,不需要保存每个f[i],程序也是异常简单:

Code
#include <stdio.h>
main()
{
int n, m, i, s=0;
printf ("N M = ");
scanf("%d%d", &n, &m);
for (i=2; i<=n; i++) s=(s+m)%i;
printf ("The winner is %d\n", s+1);
}
这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。算n,m等于一百万,一千万的情况不是问题
了。
2008年5月17日
$BEGINCUT$
$PROBLEMDESC$
$ENDCUT$
#line $NEXTLINENUMBER$ "$FILENAME$"
#include <string>
#include <vector>
#include <algorithm>
#include <numeric>

#include <iostream>
#include <sstream>
#include <queue>
#include <set>
#include <map>
#include <list>

#include <cstdio>
#include <cstdlib>
#include <cctype>
#include <cassert>

#include <cmath>
#include <complex>
using namespace std;


typedef long long int64;

#define FOR(i,a,b) for(int i=(int) (a);i<=(int)(b);++i)
#define REP(i,n) for(int i=0;i<(int)(n);++i)
#define SZ(t) ((int)((t).size()))
#define LE(t) ((int)((t).length()))
#define me0(t) memset(t,0,sizeof(t))
#define me1(t) memset(t,-1,sizeof(t))
#define out(x) (cout<<#x<<": "<<x<<endl)
#define SS stringstream
#define DB(x) (cout<<#x<<": "<<x<<endl)
#define LL long long


const double pi=acos(-1.0);
const int MAXINT = 0x7FFFFFFF;
const int64 MAXINT64 = 0x7FFFFFFFFFFFFFFFLL;



template<class T> inline void checkmax(T &a,T b)
{if(b>a) a=b;}

template<class T> inline void checkmin(T &a,T b)
{if(b<a) a=b;}

template<class T> inline T sqr(T x)
{return x*x;}

template<class T>void show(T a, int n)
{for(int i=0; i<n; ++i) cout<<a[i]<<' '; cout<<endl;}
![]()