E.恶心心的题
题意:
给一个序列 ai,q次询问,求每次LCM(al…ar,x)的值,对p取模。
思路:
线段树做法:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1e5+20;
int isprime[301];
int pri[70];
int a[maxn][65],sum[maxn<<2][65],k,s[65];
ll poww(int a,int b){//快速幂
long long ans=1;
while(b>0){
if(b&1) ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
void sieve(){
for(int i=2;i<=300;i++){
if(isprime[i]==0){
for(int j=i*i;j<=300;j+=i){
isprime[j]=1;
}
pri[++k]=i;
}
}
}
void pushup(int rt,int x){
sum[rt][x]=max(sum[rt<<1][x],sum[rt<<1|1][x]);
}
void build(int l,int r,int rt,int x){
if(l==r){
sum[rt][x]=a[l][x];
return ;
}
int m=(l+r)>>1;
build(l,m,rt<<1,x);
build(m+1,r,rt<<1|1,x);
pushup(rt,x);
}
void query(int L,int R,int l,int r,int rt){
if(L<=l&&R>=r){
for(int i=1;i<=62;i++){
s[i]=max(s[i],sum[rt][i]);
}
return ;
}
int m=(l+r)>>1;
if(L<=m) query(L,R,l,m,rt<<1);
if(R>m) query(L,R,m+1,r,rt<<1|1);
}
int main (){
int p,q,x,n;
cin>>n>>p>>q;
sieve();
for(int i=1;i<=n;i++){
scanf("%d",&x);
for(int j=1;j<=62&&x!=1;j++){
int cnt=0;
while(x%pri[j]==0){
cnt++;
x/=pri[j];
}
a[i][j]=cnt;
}
}
for(int i=1;i<=62;i++){
build(1,n,1,i);
}
while(q--){
int l,r,x,ans;
ll anss=1;
scanf("%d%d%d",&l,&r,&x);
memset(s,0,sizeof(s));
query(l,r,1,n,1);
for(int j=1;j<=62;j++){
int cnt=0;
while(x%pri[j]==0){
cnt++;
x/=pri[j];
}
ans=max(s[j],cnt);
anss=anss*poww(pri[j],ans)%p;
}
printf ("%lld\n",anss*x%p);
}
return 0;
}
st表:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1e5+20;
int isprime[300];
int pri[70],n,ans[70],k;
short int a[65][maxn],dpma[65][maxn][17];
const int mod=1e9+7;
long long poww(long long a,long long b){//快速幂
long long ans=1;
while(b>0){
if(b&1) ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
void sieve(){
for(int i=2;i<=300;i++){
if(isprime[i]==0){
for(int j=i*i;j<=300;j+=i){
isprime[j]=1;
}
pri[++k]=i;
}
}
}
void init(){
for(int i=1;i<=62;i++){
for(int j=1;j<=n;j++){
dpma[i][j][0]=a[i][j];
}
}
for(int x=1;x<=62;x++){
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
dpma[x][i][j]=max(dpma[x][i][j-1],dpma[x][i+(1<<(j-1))][j-1]);
}
}
}
}
int qa(int x ,int l, int r){
int k=log((r-l+1)*1.0)/log(2.0);
return max(dpma[x][l][k],dpma[x][r-(1<<k)+1][k]);
}
int main (){
int p,q,x;
cin>>n>>p>>q;
sieve();
for(int i=1;i<=n;i++){
scanf("%d",&x);
for(int j=1;j<=62&&x!=1;j++){
if(x%pri[j]==0){
while(x%pri[j]==0){
a[j][i]++;
x/=pri[j];
}
}
}
}
init();
while(q--){
int l,r,x,ans;
ll anss=1;
scanf("%d%d%d",&l,&r,&x);
for(int j=1;j<=62;j++){
int cnt=0;
if(x%pri[j]==0){
while(x%pri[j]==0){
cnt++;
x/=pri[j];
}
}
int t=qa(j,l,r);
ans=max(t,cnt);
anss=anss*poww(pri[j],ans)%p;
}
printf ("%lld\n",anss*x%p);
}
return 0;
}
需要注意的:
ps:虽然hdw聚聚每次的题坑点都挺多的,但写完收获也很大,吹爆hdwdl.
I.摸鱼的tomjobs2
题意:
给n个数,每一个连续区间的按位与,作为一个交叉值,求有多少个不同的交叉值。
思路:
法1:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
map<ll,int >mp;
inline ll read(){
char c=getchar();
ll f=1,x=0;
while(c<'0'||c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^'0');
c=getchar();
}
return x*f;
}
ll a[maxn],dp[maxn][32];
int n,pre[64][maxn];
void init(){
for(int i=1;i<=n;i++) dp[i][0]=a[i];
for(int j=1;(1<<j)<=n;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
dp[i][j]=dp[i][j-1]&dp[i+(1<<(j-1))][j-1];
}
}
}
ll qi(int l,int r){
int k=log((r-l+1)*1.0)/log(2.0);
return dp[l][k]&dp[r - (1 << k) + 1][k];
}
int main (){
int ans=0;
n=read();
for(int i=1; i<=n; i++){
a[i]=read();
if(mp[a[i]]==0){
mp[a[i]]=1;
ans++;
}
}
init();
for(int i=0;i<61;i++){
for(int j=1;j<=n;j++){
pre[i][j]=pre[i][j-1]+(((a[j]>>i)&1)==0);
}
}
for(int i=1; i<=n; i++){
for(int j=0;j<61;j++){
int l=i,r=n,mid,t=i-1;
while(l<=r){
mid=(l+r)/2;
if(pre[j][mid]-pre[j][i-1]==0) t=mid,l=mid+1;
else r=mid-1;
}
if(t<n) t++;
ll x=qi(i,t);
if(mp[x]==0) mp[x]=1,ans++;
}
}
printf ("%d\n",ans);
}
法2:
#include
#include
#include
#include
#include
#include
#include
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
string s1,s2;
set < ll > dp[maxn];
ll a[maxn];
map<ll ,int >mp;
int main (){
int n,ans=0;
cin>>n;
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
dp[i].insert(a[i]);
if(!mp[a[i]]){
mp[a[i]]=1;
ans++;
}
}
for(int i=1;i<n;i++){
set<ll>::iterator it;
for(it=dp[i].begin();it!=dp[i].end();it++){
dp[i+1].insert(*it&a[i+1]);
if(!mp[*it&a[i+1]]){
mp[*it&a[i+1]]=1;
ans++;
}
}
}
printf ("%d\n",ans);
}
F.打扑克牌
题意:
给你一个长度为n的数字串,你可以任意打乱顺序,求有多少个不同的数字串可以被m整除。(n<=15,m<=50)
思路:
大概是这样的
dp[3] 011 //代表你已经选了 第一个数和第二个数。
dp[2] 010 //代表选了第二个数
dp[1] 001 //代表选了第一个数
dp[3] 可以由 dp[2]和dp[1]转移
dp[2|(1<<0)]+=dp[2];
dp[1|(1<<1)]+=dp[1];
1.由 dp[2]转移相当于第一次选了第二个数第二次选第一个数。
2.由 dp[1]转移相当于第一次选了第一个数第二次选第二个数。
这个题需要再加一个模数的状态。
大概就是这样了
for(int i=0; i<(1<<n); i++) {
for(int j=0; j<n; j++){
if((i>>j)&1) continue; //表示第j个数已经选过了
for(int k=0; k<m; k++){
dp[i|(1<<j)][(k*10+a[j])%m]+=dp[i][k];
}
}
}
最后的答案就是 dp[(1<#include