以下解题思路及代码未经官方评测,仅供参考,复赛成绩以官方(CCF)评测结果为准。

1.小凯的疑惑 (math.cpp/c/pas)

【问题描述】
小凯手中有两种面值的金币,两种面值均为正整数且彼此互素。每种金币小凯都有无数个。在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付的。现在小凯想知道在无法准确支付的物品中,最贵的价值是多少金币?注意:输入数据保证存在小凯无法准确支付的商品。

【输入格式】
输入文件名为 math.in。
输入数据仅一行,包含两个正整数 a 和 b,它们之间用一个空格隔开,表示小凯手中金币的面值。

【输出格式】
输出文件名为 math.out。
输出文件仅一行,一个正整数 N,表示不找零的情况下,小凯用手中的金币不能准确支付的最贵的物品的价值。

【输入输出样例 1】
math.in
3  7
math.out
11

【数据规模与约定】
对于 30%的数据: 1 ≤ a,b ≤ 50。
对于 60%的数据: 1 ≤ a,b ≤ 10,000。
对于 100%的数据:1 ≤ a,b ≤ 1,000,000,000。

【题目解答】
数学太差只找规律吧。
设:其中一个数为2
则:2、3=>1;2、5=>3;2、7=>5;2、11=>9
得:2、n=>n-2
设:其中一个数为3
则:3、5=>7;3、7=>11;3、11=>19;3、13=>23
得:3、n=>2n-3
设:其中一个数为5
则:5、7=>23;5、11=>39;5、13=>47;5、17=>63
得:5、n=>4n-5
所以:m、n=>(m-1)n-m
#include<cstdio>
using namespace std;
int main()
{
long long a,m,n;
scanf("%lld %lld",&m,&n);
a=(m-1)*n-m;
printf("%lld",a);
return 0;
}


2.时间复杂度 (complexity.cpp/c/pas)

【问题描述】
小明正在学习一种新的编程语言 A++,刚学会循环语句的他激动地写了好多程序并给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序,于是你的机会来啦!下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正确。
A++语言的循环结构如下:
【NOIP提高组】2017复赛试题及解题报告-少儿编程教育网
其中“F i x y”表示新建变量 (i 变量 i 不可与未被销毁的变量重名)并初始化为 x,然后判断 i 和 y 的大小关系,若 i 小于等于 y 则进入循环,否则不进入。每次循环结束后i都会被修改成i +1,一旦i 大于 y 终止循环。

x和 y 可以是正整数(x 和 y 的大小关系不定)或变量 n。n 是一个表示数据规模的变量,在时间复杂度计算中需保留该变量而不能将其视为常数,该数远大于 100。

“E”表示循环体结束。循环体结束时,这个循环体新建的变量也被销毁。

注:本题中为了书写方便,在描述复杂度时,使用大写英文字母“O”表示通常意义下“Θ”的概念。

【输入格式】
输入文件名为 complexity.in。

输入文件第一行一个正整数 t,表示有 t(t ≤ 10)个程序需要计算时间复杂度。每个程序我们只需抽取其中 “F i x y”和“E”即可计算时间复杂度。注意:循环结构允许嵌套。

接下来每个程序的第一行包含一个正整数 L 和一个字符串,L 代表程序行数,字符串表示这个程序的复杂度,“O(1)”表示常数复杂度,“O(n^w)”表示复杂度为n^w,其中 w 是一个小于 100 的正整数(输入中不包含引号),输入保证复杂度只有 O(1)和 O(n^w) 两种类型。

接下来 L 行代表程序中循环结构中的“F i x y”或者 “E”。

程序行若以“F”开头,表示进入一个循环,之后有空格分离的三个字符(串)i x y,其中 i 是一个小写字母(保证不为“n”),表示新建的变量名,x 和 y 可能是正整数或 n ,已知若为正整数则一定小于 100。

程序行若以“E”开头,则表示循环体结束。

【输出格式】
输出文件名为 complexity.out。

输出文件共 t 行,对应输入的 t 个程序,每行输出“Yes”或“No”或者“ERR”(输出中不包含引号),若程序实际复杂度与输入给出的复杂度一致则输出“Yes”,不一致则输出“No”,若程序有语法错误(其中语法错误只有: F 和E 不匹配;新建的变量与已经存在但未被销毁的变量重复两种情况),则输出“ERR”。

注意:即使在程序不会执行的循环体中出现了语法错误也会编译错误,要输出 “ERR”。

【输入输出样例 1】
complexity.in
8
2  O(1)
F  i 1  1
E
2  O(n^1)
F  x 1  n
E
1  O(1)
F  x 1  n
4  O(n^2)
F  x 5  n
F  y 10  n
E
E
4  O(n^2)
F  x 9  n
E
F  y 2  n
E
4  O(n^1)
F  x 9  n
F  y n  4
E
E
4  O(1)
F  y n  4
F  x 9  n
E
E
4  O(n^2)
F  x 1  n
F  x 1  10
E
E
complexity.out
Yes
Yes
ERR
Yes
No
Yes
Yes
ERR

【数据规模与约定】
对于 30%的数据:不存在语法错误,数据保证小明给出的每个程序的前 L/2 行一定为以 F 开头的语句,第L/2+1 行至第 L 行一定为以 E 开头的语句,L<=10,若 x、y 均为整数,x 一定小于 y,且只有 y 有可能为 n。
对于50%的数据:不存在语法错误,L<=100,且若x、y均为整数,x 一定小于 y,且只有 y 有可能为 n。
对于 70%的数据:不存在语法错误,L<=100。
对于 100%的数据:L<=100。

【题目解答】
用STL stack模拟,对于用代码量堆出来的OIer来说,这就是信心倍增的大力模拟题。
代码就不上了。


3. 逛公园(park.cpp/c/pas)

【问题描述】
策策同学特别喜欢逛公园。公园可以看成一张N个点M条边构成的有向图,且没有自环和重边。其中1号点是公园的入口,N号点是公园的出口,每条边有一个非负权值,代表策策经过这条边所要花的时间。

策策每天都会去逛公园,他总是从1号点进去,从N号点出来。

策策喜欢新鲜的事物,他不希望有两天逛公园的路线完全一样,同时策策还是一个特别热爱学习的好孩子,他不希望每天在逛公园这件事上花费太多的时间。如果1号点到N号点的最短路长为d,那么策策只会喜欢长度不超过d+K的路线。

策策同学想知道总共有多少条满足条件的路线,你能帮帮他吗?

为避免输出过大,答案对P取模。

如果有无穷多条合法的路线,请输出−1。

【输入格式】
输入文件名为 park.in。
第一行包含一个整数T, 代表数据组数。
接下来T组数据,对于每组数据:
第一行包含四个整数N, M, K, P,每两个整数之间用一个空格隔开。
接下来M行,每行三个整数ai , bi , ci,代表编号为ai, bi的点之间有一条权值为ci的有向边,每两个整数之间用一个空格隔开。

【输出格式】
输出文件名为 park.out。
输出文件包含T行,每行一个整数代表答案。

【输入输出样例 1】
park.in
2
5  7 2  10
1  2  1
2  4  0
4  5  2
2  3  2
3  4  1
3  5  2
1  5  3
2  2 0  10
1  2  0
2  1  0
park.out
3
-1

【数据规模与约定】
对于不同的测试点,我们约定各种参数的规模不会超过如下:
【NOIP提高组】2017复赛试题及解题报告-少儿编程教育网

对于100%的数据, 1≤P≤109,1≤ai,bi≤𝑁,0≤ci ≤ 1000。

数据保证:至少存在一条合法的路线。

【题目解答】
最短路+拓扑排序+DP

先跑最短路。发现K只有50,所以一定是要从K入手。所以考虑DP,令f[i][j]表示走到i,多走的长度是j的方案数。(多走指的是比最短路多的部分的长度)。但是发现这个DP方程是存在环的,因为最短路径图上的边以及零边都是可以同行转移的。将最短路径图上的边以及零边都拿出来跑拓扑排序,然后让这些边在转移时必须沿着拓扑序转移即可。特别地,如果一个零环位于一条从1到n长度<=d+K的路径上,则输出-1即可。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
#include<algorithm>
#include<utility>
#define mp(A,B) make_pair(A,B)
using namespace std;
const int maxn=100010;
const int maxm=200010;
int n,m,cnt,K,P,tot,ans;
int to[maxm],next[maxm],val[maxm],head[maxn],dis[maxn],vis[maxn],d[maxn],q[maxn<<1],f[51][maxn];
int to2[maxm],next2[maxm],val2[maxm],head2[maxn],dis2[maxn];
priority_queue<pair<int,int>> pq;
inline int rd()
{
int ret=0; char gc=getchar();
while(gc<'0'||gc>'9') gc=getchar();
while(gc>='0'&&gc<='9')   ret=ret*10+gc-'0',gc=getchar();
return ret;
}
inline void add(int a,int b,int c)
{
to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt;
to2[cnt]=a,val2[cnt]=c,next2[cnt]=head2[b],head2[b]=cnt++;
}
inline void upd(int &x,int y)
{
x=(x+y)%P;
}
void work()
{
n=rd(),m=rd(),K=rd(),P=rd();
int i,j,k,a,b,c,u;
memset(head,-1,sizeof(head)),memset(head2,-1,sizeof(head2)),cnt=tot=ans=0;
for(i=1;i<=m;i++)    a=rd(),b=rd(),c=rd(),add(a,b,c);
//1
memset(vis,0,sizeof(vis)),memset(dis,0x3f,sizeof(dis)),memset(d,0,sizeof(d));
pq.push(mp(0,1)),dis[1]=0;
while(!pq.empty())
{
u=pq.top().second,pq.pop();
if(vis[u])  continue;
vis[u]=1;
for(i=head[u];i!=-1;i=next[i])  if(dis[to[i]]>dis[u]+val[i])dis[to[i]]=dis[u]+val[i],pq.push(mp(-dis[to[i]],to[i]));
}
//2
memset(dis2,0x3f,sizeof(dis2)),memset(vis,0,sizeof(vis));
pq.push(mp(0,n)),dis2[n]=0;
while(!pq.empty())
{
u=pq.top().second,pq.pop();
if(vis[u])  continue;
vis[u]=1;
for(i=head2[u];i!=-1;i=next2[i])    if(dis2[to2[i]]>dis2[u]+val2[i])dis2[to2[i]]=dis2[u]+val2[i],pq.push(mp(-dis2[to2[i]],to2[i]));
}
//3
for(i=1;i<=n;i++)    for(j=head[i];j!=-1;j=next[j])  if(dis[i]+val[j]==dis[to[j]])   d[to[j]]++;
for(i=1;i<=n;i++)    if(!d[i])  q[++tot]=i;
for(j=1;j<=tot;j++)
{
u=q[j];
for(i=head[u];i!=-1;i=next[i])  if(dis[u]+val[i]==dis[to[i]])
{
d[to[i]]--;
if(!d[to[i]])   q[++tot]=to[i];
}
}
for(i=1;i<=n;i++)   if(d[i]&&dis[i]+dis2[i]<=dis[n]+K)
{
printf("-1\n");
return ;
}
//DP
memset(f,0,sizeof(f));
f[0][1]=1;
for(k=0;k<=K;k++)
{
for(i=1;i<=tot;i++)  for(u=q[i],j=head[u];j!=-1;j=next[j])   if(dis[u]+val[j]==dis[to[j]])
{
upd(f[k][to[j]],f[k][u]);
}
for(i=1;i<=n;i++) for(j=head[i];j!=-1;j=next[j]) if(dis[i]+val[j]!=dis[to[j]]&&k+dis[i]+val[j]-dis[to[j]]<=K)
{
upd(f[k+dis[i]+val[j]-dis[to[j]]][to[j]],f[k][i]);
}
}
for(i=0;i<=K;i++)    upd(ans,f[i][n]);
printf("%d\n",ans);
}
int main()
{
int T=rd();
while(T--) work();
return 0;
}


-Day2-

1.奶酪 (cheese.cpp/c/pas)

【问题描述】
现有一块大奶酪,它的高度为 h,它的长度和宽度我们可以认为是无限大的,奶酪中间有许多半径相同的球形空洞。我们可以在这块奶酪中建立空间坐标系,在坐标系中,奶酪的下表面为z = 0,奶酪的上表面为z = h。

现在,奶酪的下表面有一只小老鼠 Jerry,它知道奶酪中所有空洞的球心所在的坐标。如果两个空洞相切或是相交,则 Jerry 可以从其中一个空洞跑到另一个空洞,特别地,如果一个空洞与下表面相切或是相交,Jerry则可以从奶酪下表面跑进空洞;如果一个空洞与上表面相切或是相交,Jerry 则可以从空洞跑到奶酪上表面。

位于奶酪下表面的 Jerry 想知道,在不破坏奶酪的情况下,能否利用已有的空洞跑到奶酪的上表面去?

空间内两点P(x ,y ,z )、P(x ,y ,z )的距离公式如下:
【NOIP提高组】2017复赛试题及解题报告-少儿编程教育网
【输入格式】
输入文件名为 cheese.in。

每个输入文件包含多组数据。

输入文件的第一行,包含一个正整数 T,代表该输入文件中所含的数据组数。

接下来是 T 组数据,每组数据的格式如下:

第一行包含三个正整数 n,h 和 r,两个数之间以一个空格分开,分别代表奶酪中空洞的数量,奶酪的高度和空洞的半径。

接下来的 n 行,每行包含三个整数 x、y、z,两个数之间以一个空格分开,表示空洞球心坐标为(x, y, z)。

【输出格式】
输出文件名为 cheese.out。

输出文件包含 T 行,分别对应 T 组数据的答案,如果在第 i 组数据中,Jerry 能从下表面跑到上表面,则输出“Yes”,如果不能,则输出“No”(均不包含引号)。

【输入输出样例 1】
cheese.in
3
2  4  1
0  0  1
0  0  3
2  5  1
0  0  1
0  0  4
2  5  2
0  0  2
2  0  4
cheese.out
Yes
No
Yes

【数据规模与约定】
对于 20%的数据,n = 1,1 ≤ h , r ≤ 10,000,坐标的绝对值不超过 10,000。
对于 40%的数据,1 ≤ n ≤ 8, 1 ≤ h , r ≤ 10,000,坐标的绝对值不超过 10,000。
对于 80%的数据,1 ≤ n ≤ 1,000,1 ≤ h , r ≤ 10,000,坐标的绝对值不超过 10,000。
对于 100%的数据,1 ≤ n ≤ 1,000,1 ≤ h , r ≤ 1,000,000,000,T ≤ 20,坐标的绝对值不超过 1,000,000,000。

【题目解答】
真正的T1,并查集+高二数学常识。

用并查集做,将位置相距小于等于2倍半径的归为一个集合。注意:Z位置+半径>=H的,与1001归为一个集合。Z位置-半径<=0的,与0归为一个集合。最后看0和1001是否在一个集合中。
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<iomanip>
#include<cstring>
#include<algorithm>
#include<ctime>
using namespace std;
int t,n,h,r,x,y,z,f,fa[1002],t1,t2;
struct sb
{
int x;
int y;
int z;
}a[1001];
intfind(int k)
{
if(fa[k]!=k)
fa[k]=find(fa[k]);
return fa[k];
}
int main()
{
cin>>t;
for(int i=1;i<=t;i++)
{
cin>>n>>h>>r;
for(int i=1;i<=n;i++)
fa[i]=i;
fa[0]=0;
fa[1001]=1001;
for(int j=1;j<=n;j++)
{
cin>>a[j].x>>a[j].y>>a[j].z;
if(a[j].z+r>=h)
{
t1=find(j);
t2=find(1001);
fa[t1]=t2;
}
if(a[j].z-r<=0)
{
t1=find(j);
t2=find(0);
fa[t1]=t2;
}
for(int w=1;w<j;w++)
{
if(sqrt((a[j].x-a[w].x)*(a[j].x-a[w].x)+(a[j].y-a[w].y)*(a[j].y-a[w].y)+(a[j].z-a[w].z)*(a[j].z-a[w].z))<=2*r)
{
t1=find(j);
t2=find(w);
fa[t1]=t2;
}
}
}
t1=find(0);
t2=find(1001);
if(t1==t2)
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
}
return 0;
}


2. 宝藏 (treasure.cpp/c/pas)

【问题描述】
参与考古挖掘的小明得到了一份藏宝图,藏宝图上标出了 n 个深埋在地下的宝藏屋,也给出了这 n 个宝藏屋之间可供开发的 m 条道路和它们的长度。

小明决心亲自前往挖掘所有宝藏屋中的宝藏。但是,每个宝藏屋距离地面都很远,也就是说,从地面打通一条到某个宝藏屋的道路是很困难的,而开发宝藏屋之间的道路则相对容易很多。

小明的决心感动了考古挖掘的赞助商,赞助商决定免费赞助他打通一条从地面到某个宝藏屋的通道,通往哪个宝藏屋则由小明来决定。

在此基础上,小明还需要考虑如何开凿宝藏屋之间的道路。已经开凿出的道路可以任意通行不消耗代价。每开凿出一条新道路,小明就会与考古队一起挖掘出由该条道路所能到达的宝藏屋的宝藏。另外,小明不想开发无用道路,即两个已经被挖掘过的宝藏屋之间的道路无需再开发。

新开发一条道路的代价是:

这条道路的长度 × 从赞助商帮你打通的宝藏屋到这条道路起点的宝藏屋所经过的宝藏屋的数量(包括赞助商帮你打通的宝藏屋和这条道路起点的宝藏屋)。

请你编写程序为小明选定由赞助商打通的宝藏屋和之后开凿的道路,使得工程总代价最小,并输出这个最小值。

【输入格式】
输入文件名为 treasure.in。

第一行两个用空格分离的正整数 n 和 m,代表宝藏屋的个数和道路数。

接下来 m 行,每行三个用空格分离的正整数,分别是由一条道路连接的两个宝藏屋的编号(编号为 1~n),和这条道路的长度 v。

【输出格式】
输出文件名为 treasure.out。

输出共一行,一个正整数,表示最小的总代价。

【输入输出样例 1】
treasure.in
4  5
1  2  1
1  3  3
1  4  1
2  3  4
3  4  1
treasure.out
4

【样例输入输出 2】
treasure.in
4  5
1  2  1
1  3  3
1  4  1
2  3  4
3  4  2
treasure.out
5

【数据规模与约定】
对于 20%的数据:
保证输入是一棵树,1≤n≤8,v≤5000 且所有的 v 都相等。
对于 40%的数据:
1≤n≤8,0≤m≤1000,v≤5000 且所有的 v 都相等。
对于 70%的数据:
1≤n≤8,0≤m≤1000,v≤ 5000
对于 100%的数据:
1≤n≤12,0≤m≤1000,v≤ 500000

【题目解答】
状压DP

一看n<=12,明显是状压DP的数据范围。于是令f[i][S]表示当前与根连通的点的状态为S,并且最深的点的深度为i的最小代价。转移时,我们枚举所有不在S中的点,处理出每个点与S中的某个点连通所需要的最小代价。然后枚举这些点构成的所有集合S',用S'中所有点的代价+f[i][S]去更新f[i+1][S|S']即可。时间复杂度:O(n3n)。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
typedef long long ll;
int n,m,tot,ans;
int map[110][110],dis[110][110],Log[4100];
int f[15][4100],g[4100],ref[4100],v[15],p[15];
inline int min(const int &a,const int &b)
{
return a<b?a:b;
}
int main()
{
scanf("%d%d",&n,&m);
register int i,j,a,b,c,x;
for(i=0;i<n;i++) for(j=0;j<n;j++)map[i][j]=60000000;
for(i=1;i<=m;i++)
{
scanf("%d%d%d",&a,&b,&c),a--,b--;
map[a][b]=map[b][a]=min(map[a][b],c);
}
for(i=0;i<n;i++) Log[1<<i]=i;
memset(f,0x3f,sizeof(f));
for(i=0;i<n;i++) f[0][1<<i]=0;
for(i=0;i<n;i++)for(x=0;x<(1<<n);x++)
{
tot=0;
for(a=0;a<n;a++) if(!(x&(1<<a)))
{
v[tot]=60000000,p[tot]=1<<a;
for(j=x;j;j-=j&-j)
{
b=Log[j&-j];
v[tot]=min(v[tot],map[a][b]*(i+1));
}
tot++;
}
for(j=1;j<(1<<tot);j++)
{
g[j]=g[j-(j&-j)]+v[Log[j&-j]];
ref[j]=ref[j-(j&-j)]|p[Log[j&-j]];
f[i+1][x|ref[j]]=min(f[i+1][x|ref[j]],f[i][x]+g[j]);
}
}
ans=1<<30;
for(i=0;i<=n;i++)    ans=min(ans,f[i][(1<<n)-1]);
printf("%d",ans);
return 0;
}


3.列队 (phalanx.cpp/c/pas)

【问题描述】
Sylvia是一个热爱学习的女孩子。

前段时间,Sylvia 参加了学校的军训。众所周知,军训的时候需要站方阵。Sylvia 所在的方阵中有n × m名学生,方阵的行数为 n,列数为 m。

为了便于管理,教官在训练开始时,按照从前到后,从左到右的顺序给方阵中的学生从 1 到 n × m 编上了号码(参见后面的样例)。即:初始时,第 i 行第 j 列的学生的编号是(i−1)×m+j。

然而在练习方阵的时候,经常会有学生因为各种各样的事情需要离队。在一天中,一共发生了q件这样的离队事件。每一次离队事件可以用数对(x,y) (1≤x≤n, 1≤y≤m)描述,表示第 x 行第 y 列的学生离队。

在有学生离队后,队伍中出现了一个空位。为了队伍的整齐,教官会依次下达这样的两条指令:

1.向左看齐。这时第一列保持不动,所有学生向左填补空缺。不难发现在这条指令之后,空位在第 x 行第 m列。

2.向前看齐。这时第一行保持不动,所有学生向前填补空缺。不难发现在这条指令之后,空位在第 n 行第 m列。

教官规定不能有两个或更多学生同时离队。即在前一个离队的学生归队之后,下一个学生才能离队。因此在每一个离队的学生要归队时,队伍中有且仅有第 n 行第 m 列一个空位,这时这个学生会自然地填补到这个位置。

因为站方阵真的很无聊,所以 Sylvia 想要计算每一次离队事件中,离队的同学的编号是多少。

注意:每一个同学的编号不会随着离队事件的发生而改变,在发生离队事件后方阵中同学的编号可能是乱序的。

【输入格式】
输入文件名为 phalanx.in。

输入共 q+1 行。

第 1 行包含 3 个用空格分隔的正整数 n, m, q,表示方阵大小是n行 m 列,一共发生了 q 次事件。

接下来 q 行按照事件发生顺序描述了 q 件事件。每一行是两个整数 x, y,用一个空格分隔,表示这个离队事件中离队的学生当时排在第 x 行第 y 列。

【输出格式】
输出文件名为 phalanx.out。

按照事件输入的顺序,每一个事件输出一行一个整数,表示这个离队事件中离队学生的编号。

【输入输出样例 1】
phalanx.in
2  2  3
1  1
2  2
1  2
phalanx.out
1
1
4

【数据规模与约定】
【NOIP提高组】2017复赛试题及解题报告-少儿编程教育网

数据保证每一个事件满足 1≤x≤n,1≤y≤m。

【题目解答】
离线+平衡树

由于操作是可逆的,所以将所有操作反过来做,将人的移动改成询问的反向移动,那么只需要维护这些询问点的移动即可,不难想到用数据结构。将操作顺序反过来,那么一次操作(x,y)可以看成:

1.(n,m)的学生出队;

2.第n列中,第x行及以下的所有学生下移一格;

3.第x行中,第y列及右面的所有学生右移一格;

4.将当前询问的学生放到(x,y)处。

鉴于这些操作,可以选择平衡树,那么1操作就是单点删除,2,3操作就是区间加法,4操作就是单点加入。对每一行,以及最后一列都开一棵SBT,维护其中的所有询问即可。并且注意:每一行y的范围是[1,m),即如果一行的最右面的元素位于最后一列,要将其从这一行删除,并加入到最后一列中。还有一点,如果(n,m)处有询问,说明那个询问与当前询问是重合的,所以记录一个类似于pre的东西,将那个询问的pre指向当前询问,并将那个询问删除。输出答案的时候直接令该询问的答案等于其pre的答案即可。
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=300010;
typedef long long ll;
struct node
{
int ch[2],siz,val,org,tag;
}s[maxn<<1];
int n,m,q,tot;
int rx,ry[maxn];
int bel[maxn],x[maxn],y[maxn];
ll ans[maxn];
inline int rd()
{
int ret=0; char gc=getchar();
while(gc<'0'||gc>'9') gc=getchar();
while(gc>='0'&&gc<='9')   ret=ret*10+gc-'0',gc=getchar();
return ret;
}
inline void pushdown(int x)
{
if(s[x].tag)
{
if(s[x].ch[0]) s[s[x].ch[0]].val+=s[x].tag,s[s[x].ch[0]].tag+=s[x].tag;
if(s[x].ch[1]) s[s[x].ch[1]].val+=s[x].tag,s[s[x].ch[1]].tag+=s[x].tag;
s[x].tag=0;
}
}
inline void pushup(int x)
{
s[x].siz=s[s[x].ch[0]].siz+s[s[x].ch[1]].siz+1;
}
inline void rotate(int &x,int d)
{
int y=s[x].ch[d];
pushdown(x),pushdown(y);
s[x].ch[d]=s[y].ch[d^1],s[y].ch[d^1]=x;
pushup(x),pushup(y);
x=y;
}
inline void maintain(int &x,int d)
{
if(s[s[s[x].ch[d]].ch[d]].siz>s[s[x].ch[d^1]].siz)   rotate(x,d);
else   if(s[s[s[x].ch[d]].ch[d^1]].siz>s[s[x].ch[d^1]].siz)rotate(s[x].ch[d],d^1),rotate(x,d);
else   return ;
maintain(s[x].ch[d],d),maintain(s[x].ch[d^1],d^1);
maintain(x,d),maintain(x,d^1);
}
void insert(int &x,int y,int z)
{
if(!x)
{
x=++tot,s[x].siz=1,s[x].ch[0]=s[x].ch[1]=s[x].tag=0,s[x].val=y,s[x].org=z;
return ;
}
pushdown(x);
int d=(y>s[x].val);
insert(s[x].ch[d],y,z),pushup(x);
maintain(x,d);
}
void del(int &x,int y)
{
s[x].siz--,pushdown(x);
if(s[y].val>s[x].val)    del(s[x].ch[1],y);
else if(s[y].val<s[x].val)   del(s[x].ch[0],y);
else
{
if(!s[x].ch[0]||!s[x].ch[1])
{
x=s[x].ch[0]^s[x].ch[1];
return ;
}
int u=s[x].ch[1];   pushdown(u);
while(s[u].ch[0])   u=s[u].ch[0],pushdown(u);
s[x].org=s[u].org,s[x].val=s[u].val;
del(s[x].ch[1],u);
}
}
void updata(int x,int y)
{
if(!x) return ;
pushdown(x);
if(s[x].val>=y)
{
s[x].val++;
if(s[x].ch[1])  s[s[x].ch[1]].val++,s[s[x].ch[1]].tag++;
updata(s[x].ch[0],y);
}
else   updata(s[x].ch[1],y);
}
inline int findmax(int x)
{
pushdown(x);
while(s[x].ch[1])   x=s[x].ch[1],pushdown(x);
return x;
}
inline ll point(int a,int b) {return ll(a-1)*m+b;}
void dfs(int x,int t)
{
if(!x) return ;
pushdown(x);
if(!t) ans[s[x].org]=point(s[x].val,m);
else   ans[s[x].org]=point(t,s[x].val);
dfs(s[x].ch[0],t),dfs(s[x].ch[1],t);
}
int find(int x,int y)
{
if(!x) return 0;
pushdown(x);
if(y>s[x].val)   return find(s[x].ch[1],y);
if(y<s[x].val)   return find(s[x].ch[0],y);
return x;
}
int main()
{
n=rd(),m=rd(),q=rd();
int i;
for(i=1;i<=q;i++)
{
bel[i]=i;
x[i]=rd(),y[i]=rd();
}
for(i=q;i>=1;i--)
{
int t=findmax(rx);
if(s[t].val==n)
{
bel[s[t].org]=i,del(rx,t);
}
updata(rx,x[i]);
updata(ry[x[i]],y[i]);
t=findmax(ry[x[i]]);
if(s[t].val==m)
{
del(ry[x[i]],t),insert(rx,x[i],s[t].org);
}
if(y[i]<m)   insert(ry[x[i]],y[i],i);
else   insert(rx,x[i],i);
}
dfs(rx,0);
for(i=1;i<=n;i++)    dfs(ry[i],i);
for(i=1;i<=q;i++)   printf("%lld\n",ans[i]=ans[bel[i]]);
return 0;
}