390 A. Inna and Alarm Clock
题目:http://codeforces.com/problemset/problem/390/A
题意比较简单,每次操作消除一整行或一整列的点,问最少的操作次数。
但是题目给了一个限制,所有操作要么全部都是消除一整行,要么都是消除一整列。
有这个限制就好处理了,求出不同横坐标的个数和不同纵坐标的个数,两者取较小者就是答案。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, i, j, a, b, x[101], y[101];
int main(){
while(~scanf("%d", &n)){
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
while(n--){
scanf("%d %d", &i, &j);
x[i]++;
y[j]++;
}
a=b=0;
for(i=0; i<=100; i++){
if(x[i]) a++;
if(y[i]) b++;
}
printf("%d\n", min(a,b));
}
return 0;
}
题目:http://codeforces.com/problemset/problem/390/B
题目给两个序列A和B,对于Bi,要找到一对xi和yi,使得xi+yi=Bi,并且1<=xi, yi<=Ai。
然后有一个值C,初始为0。
如果找到就把xi*yi加进去,否则就C-1。
问C最大能多少。
由xi和yi的条件能得到2<=xi+yi<=2*Ai,所以只要Bi能落在这个区间就能找到合适的xi和yi。
而要使xi*yi尽可能大,就得让xi和yi尽可能接近。所以令xi=[b/2],yi=bi-xi。
第一次交的时候忘记判断bi等于1的情况WA掉了,> <
#include<cstdio>
int n, a[100000], b, i, j;
long long ans, tmp1, tmp2;
int main(){
while(~scanf("%d", &n)){
ans=0;
for(i=0; i<n; i++){
scanf("%d", a+i);
}
for(i=0; i<n; i++){
scanf("%d", &b);
if(b==1){
ans--;
continue;
}
j=b>>1;
b-=j;
if(j>a[i] || b>a[i]) ans--;
else{
tmp1=j; tmp2=b;
ans+=tmp1*tmp2;
}
}
printf("%I64d\n", ans);
}
return 0;
}
390 C. Inna and Candy Boxes
题目:http://codeforces.com/problemset/problem/390/C
给定一个长度N的01串S,再给一个整数K。
然后有W个询问,每个询问给一对整数L和R,问对原串在[L,R]这个区间上要修改多少个位置使得在L+K-1,L+2K-1,L+3K-1,...,R都是1,而其它位置都是0。题目保证R-L+1能够被K整除。
对于一次询问,首先求出原串中对应区间上1的个数X,和落在指定位置上1的个数Y,以及指定位置的个数Z。
那么X-Y就是需要将1修改为0的个数,Z-Y就是需要将0修改为1的个数,两者相加就是当前询问的答案。
对于X的计算,因为每次询问都不会对原串做修改,所以可以用一个数组a[]预处理前缀和,a[i]代表[1,i]这个区间上1的个数,X=a[R]-a[L-1]。
Z的计算也很简单,等差数列求项数而已,Z=(R-L+1)/k。
剩下的就是Y的计算,因为K不超过10,而指定位置肯定是模k同余,所以可以预处理每个位置i,S[i]如果是0就忽略,如果是1,就在b[i%k][i]这个位置赋值为1,然后对于每个模的值也计算前缀和,对于询问,Y=b[r%k][r]-b[r%k][l-1]。
这样就能求出答案了。
#include<cstdio>
#include<cstring>
int n, k, w, i, j, l, r, a[100010], b[10][100010];
char s[100010];
int main(){
while(~scanf("%d %d %d", &n, &k, &w)){
scanf("%s", s);
memset(b,0,sizeof(b));
a[0]=0;
for(i=1; i<=n; i++){
if(s[i-1]=='0') a[i]=0;
else{
a[i]=1;
b[i%k][i]=1;
}
}
for(i=2; i<=n; i++) a[i]+=a[i-1];
for(i=0; i<k; i++){
for(j=2; j<=n; j++) b[i][j]+=b[i][j-1];
}
while(w--){
scanf("%d %d", &l, &r);
i = a[r]-a[l-1];
j = b[r%k][r]-b[r%k][l-1];
l = (r-l+1)/k;
printf("%d\n", i-j+l-j);
}
}
return 0;
}
390 D. Inna and Sweet Matrix
题目:http://codeforces.com/problemset/problem/390/D
给一个N*M的矩阵,初始全部为空,然后要在上面放K(K<=M*N)个糖果,对于放糖果的位置(X,Y),必须将糖果从(1,1)位置开始移动过去,并且移动路径不能有其他糖果,每次移动的消耗就是路径长度。问放置所有糖果的最小消耗,放糖果的位置是任意的。
输出最小的消耗和K个糖果的移动路径。
要让消耗最小就必须让所有糖果的位置是距离(1,1)最近的K个位置。又因为放置的糖果不能影响到后面糖果的移动,所以在K个位置中越远的越要先放。
我采取的策略是从(1,1)开始BFS,每次向右走或者向下走,并且标记访问到的点,访问K个点的时候停止搜索。这样访问到的K个点就是可以放的并且消耗最小的。
然后从后面往前扫用于标记的数组,遇到一个访问的就输出路径,这样就能保证先放的点不会妨碍到后放的点。
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct Point{
int x, y, d;
Point(){}
Point(int _x, int _y, int _d){
x=_x; y=_y; d=_d;
}
}p;
int n,m,k,t,ans,i,j,a,b;
int xl[2]={0,1};
int yl[2]={1,0};
bool vis[51][51];
void print(int x, int y){
for(int xx=1; xx<=y; xx++) printf("(1,%d) ", xx);
for(int xx=2; xx<=x; xx++) printf("(%d,%d) ", xx, y);
puts("");
}
int main(){
while(~scanf("%d %d %d", &n, &m, &k)){
memset(vis,0,sizeof(vis));
ans=1;
t=1;
queue<Point> Q;
Q.push(Point(1,1,1));
vis[1][1]=1;
while(t<k){
p = Q.front(); Q.pop();
for(i=0; i<2; i++){
a=p.x+xl[i];
b=p.y+yl[i];
if(a>n || b>m) continue;
if(vis[a][b]) continue;
vis[a][b]=1;
ans+=p.d+1;
t++;
Q.push(Point(a,b,p.d+1));
if(t>=k) break;
}
}
printf("%d\n", ans);
for(i=n; i>0; i--){
for(j=m; j>0; j--){
if(vis[i][j]){
print(i,j);
}
}
}
}
return 0;
}
390 E. Inna and Large Sweet Matrix
题目:http://codeforces.com/problemset/problem/390/E
又是一个N*M的矩阵,初始全部为0;
有两种操作:
1、对于(x1,y1)到(x2,y2)这个矩形,所有值增加v;
2、求出(x1,y1)到(x2,y2)这个矩形内数字的和A,所有满足p<x1或p>x2并且q<y1或q>y2的点的和B,输出A-B;
咋一看像二维线段树,可是N和M可以达到4*6^10,好吧,放弃这个念头,换种思路。
举样例来说,对于第四个查询(1,2,3,3,4),可以得到下面的情况:
蓝色部分是我们要的A,绿色部分是B,把红色部分(蓝色也算进去)记为C,黄色部分(蓝色也算进去)记为D。
那么C就是当前矩阵所有满足x1<=x<=x2的点的和,D是所有满足y1<=y<=y2的点的和。
把当前矩阵内所有点的和记为sum,我们可以得到下面的等式:
C+D-A+B=sum,
整理可以得到:C+D-sum=A-B,而题目要我们求的正是A-B,所以问题可以转化成求C+D-sum。
对于sum,对于每次操作1,sum+=(x2-x1+1)*(y2-y1+1)*v,这个很好求;
而C,如果用S[i]来表示所有x=i的格子内数字的和,我们可以知道对于每个操作1,对于每个[x1,x2]上的i,S[i]+=(y2-y1+1)*v;
这样对于C的处理就变成一个区间更新和区间查询的问题了。
D也可以同样处理。
可是这题目N和M太大啦,比赛的时候开两个线段树就给MLE了。
记得以前看过可以用树状数组解决这种问题,可尼玛地不懂写啊= =
于是趁这机会学一下,虽然比赛的最后还是赶不上。。。
#include<cstdio>
#include<cstring>
#define LL long long
#define MAXN 4000001
int n, m, q, k, op, x1, x2, y1, y2, lim;
LL sum, s1[2][MAXN], s2[2][MAXN];
inline int lowbit(int x){
return x&(-x);
}
void add(LL a[], int x, LL v){
for(;x<=lim;x+=lowbit(x)) a[x]+=v;
}
void update(int l, int r, LL v){
add(s1[k], l, v);
add(s1[k], r+1, -v);
add(s2[k], l, v*l);
add(s2[k], r+1, -v*(r+1));
}
LL cal(LL a[], int x){
LL re=0;
for(;x;x-=lowbit(x)) re+=a[x];
return re;
}
LL query(int l, int r){
LL tmp1= (r+1)*cal(s1[k], r) - cal(s2[k], r);
LL tmp2= l*cal(s1[k], l-1) - cal(s2[k], l-1);
return tmp1-tmp2;
}
inline void GN(int& x){
char c=getchar();
x=0;
while(c<48 || c>57) c=getchar();
while(c>=48 && c<=57){
x=x*10+c-48;
c=getchar();
}
}
inline void GN(LL& x){
char c=getchar();
x=0;
while(c<48 || c>57) c=getchar();
while(c>=48 && c<=57){
x=x*10+c-48;
c=getchar();
}
}
int main(){
LL v;
while(~scanf("%d %d %d", &n, &m, &q)){
sum=0;
while(q--){
GN(op); GN(x1); GN(y1); GN(x2); GN(y2);
if(op){
k=0; lim=n;
LL tmp1=query(x1,x2);
k=1; lim=m;
LL tmp2=query(y1,y2);
printf("%I64d\n", tmp1+tmp2-sum);
}
else{
GN(v);
LL tmp1=x2-x1+1;
LL tmp2=y2-y1+1;
sum+=tmp1*tmp2*v;
k=0; lim=n;
update(x1,x2,v*tmp2);
k=1; lim=m;
update(y1,y2,v*tmp1);
}
}
}
return 0;
}