点击这里到杭电,里面都是easy problem
An easy problemTime Limit: 8000/5000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 3645 Accepted Submission(s): 1334 Problem Description One day, a useless calculator was being built by Kuros. Let's assume that number X is showed on the screen of calculator. At first, X = 1. This calculator only supports two types of operation.
Input The first line is an integer T(1≤T≤10 ), indicating the number of test cases.
Output For each test case, the first line, please output "Case #x:" and x is the id of the test cases starting from 1.
Sample Input
1 10 1000000000 1 2 2 1 1 2 1 10 2 3 2 4 1 6 1 7 1 12 2 7
Sample Output
Case #1: 2 1 2 20 10 1 6 42 504 84 |
这道题一点都不easy好吧。。。可能是我的English太垃圾了吧,看了几眼题目,试了一下样例,本以为我以为的就是我以为的,没想到纠结了一个多小时居然是因为把题看错了☹。原来除法操作除的是后面数字做标号的被乘的数
看到这个题的限时5000ms,实话告诉我,你有什么想法呢?暴力!啊哈哈哈哈哈,果然这道题暴力能过得去,不过暴力也是要有技巧的,据说乘法能比除法省上3-5倍的时间 ,我的老天爷,怪不得我用除法会被TLE呢。。。
其实无论是线段树还是暴力,必经之路都是将除法转化为乘法,那么怎么转换呢?
其实这道题的每一个输出结果都是由前面的数据来决定的,就拿简单的样例来说吧
设被操作数存在数组a[]中 第6个结果的由来就是 1*a[1]/a[1]*a[3]*a[4]/a[3]/a[4]
你会发现分子分母是有重复数字的,而且除号后面的数字只是一个索引并不会被乘法操作
并且题目中说了,除法操作不会有相同的被操作数,这就给我们提供了便利
我们可以开一个bool数组来标记这个数是否会被乘法操作,如果会,就把他留着,不会的话,就把他删掉 ,被除掉的数也同样标记为不会被操作,然后每一个结果都相当于从前面开始,将留下的数累乘起来
仔细想想是不是这个道理
枚举固然简单,幸运的是这道题的限时比较长,算是比较仁慈,我的暴力代码用G++跑了3978ms,C++还是被TLE
而线段树就不一样了,用G++跑了2371ms,C++跑了2402ms,无论是哪个,效率提高了一倍呢!
这要是限时改成3000ms暴力不就GG了吗。
不过线段树这玩意确实挺难的,让人脑阔疼,还是在大佬的指点下才摸出来的,太卑微了。。。Orz
// easy 暴力
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
const int mm=1e5+5;
int t;
int n,mod;
int op;
ll aa[mm];
int book[mm];
int test=1;
int main()
{
scanf("%d",&t);
while(t--){
memset(book,0,sizeof(book));
scanf("%d%d",&n,&mod);
printf("Case #%d:\n",test++);
ll res=1;
for(int i=1;i<=n;i++){
scanf("%d%lld",&op,&aa[i]);
if(op==1){
book[i]=1;//乘法数字留下
res=res*aa[i]%mod;
printf("%lld\n",res);
}
else {
res=1;
book[aa[i]]=0;//除法的数抹掉
for(int j=1;j<i;j++)
if(book[j])//除去某个数 相当于之前没乘过他
res=res*aa[j]%mod;
printf("%lld\n",res);
}
// for(int i=1;i<=n;i++)
// cout<<book[i]<<' ';
// cout<<endl;
// for(int i=1;i<=n;i++)
// cout<<aa[i]<<' ';
// printf("\n");
}
}
return 0;
}
还有高级一点的线段树,的确有点NAN
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mm=1e5+5;
#define ll long long
int n,mod;
int book[mm];
ll a[mm];
struct node{
int l,r;
ll lazy;
}pp[mm<<2];
void build(int k,int l,int r){
pp[k].l=l;
pp[k].r=r;
pp[k].lazy=1;//初始化
if(l==r)return ;
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
}
void pd(int k){
pp[k<<1].lazy=(pp[k].lazy*pp[k<<1].lazy)%mod;
pp[k<<1|1].lazy=(pp[k].lazy*pp[k<<1|1].lazy)%mod;
pp[k].lazy=1;
}
void change(int k,int l,int r,ll x){//
if(pp[k].l>=l&&pp[k].r<=r){
pp[k].lazy=(pp[k].lazy*x)%mod;//乘以 x 然后mod
return ;
}
pd(k);//深入
int mid=(pp[k].l+pp[k].r)>>1;
if(l<=mid)change(k<<1,l,r,x);
if(r>mid)change(k<<1|1,l,r,x);
}
ll query(int k,int pos){//查询区间 [1,pos] 的计算结果
if(pp[k].l==pp[k].r)
return pp[k].lazy%mod;//深入到了这个点 return
pd(k);
int mid=(pp[k].l+pp[k].r)>>1;
if(pos<=mid)
return query(k<<1,pos);//
else return query(k<<1|1,pos); //
}
int main()
{
int t;
int test=1;
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&mod);
memset(book,0,sizeof(book));
build(1,1,n);
for(int i=1;i<=n;i++){
int op,x;
scanf("%d%d",&op,&x);
if(op==2){
change(1,x,i-1,a[x]);//乘到他的前面 乘法的地方
book[i]=book[x]=1;// 标记这个数被除掉了
}
else a[i]=(ll)x;// 乘法将 x 存入a[]
}
for(int i=1;i<=n;i++)
if(book[i]==0)
change(1,i,n,a[i]);//将a[i]乘进去
printf("Case #%d:\n",test++);
for(int i=1;i<=n;i++)
printf("%lld\n",query(1,i));//查询区间 [1,i]的计算结果
}
return 0;
}