一些好题和没完全理解的题
luogu P1996 火柴排队
离散化的思想很巧妙
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int P=99999997;
struct EDG
{
int x,y;
}a[200001],b[200001];
bool cmp(EDG a,EDG b) {return a.x<b.x;}
int cnt,n,m,tree[200001],ans,c[200001],d[200001],e[200001];
int lowbit(int x){
return x&(-x);
}
void add(int x,int y)
{
while(x<=n)
{
tree[x]+=y;
x+=lowbit(x);
}
}
int get(int x)
{
int res=0;
while(x)
{
res+=tree[x];
x-=lowbit(x);
}
return res;
}
signed main()
{
// freopen("in.txt","r",stdin);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].x,a[i].y=i;
for(int i=1;i<=n;i++) cin>>b[i].x,b[i].y=i;
sort(a+1,a+n+1,cmp);
sort(b+1,b+n+1,cmp);
for(int i=1;i<=n;i++) e[a[i].y]=b[i].y;
for(int i=1;i<=n;i++)
{
add(e[i],1);
ans+=(i-get(e[i]));
ans%=P;
}
cout<<ans%P;
}
luogu P1262 间谍网络
拓扑思想很重要,在一个dag上,只要将所有入度为零的
点控制,则一定能控制整张图
#include<bits/stdc++.h>
using namespace std;
queue<int>q;
const int N=200001;
int a[N],flag[N],vis[N],n,m,u[N],v[N],w[N],nxt[N],fst[N],tot,U[N],V[N],W[N],NXT[N],FST[N],TOT,color[N],Ans[N];
int struck[N],head,low[N],dfn[N],VIS[N],rd[N];
int sum,ans,minn;
void add(int x,int y)
{
tot++;
u[tot]=x;
v[tot]=y;
nxt[tot]=fst[x];
fst[x]=tot;
}
void ADD(int x,int y)
{
TOT++;
U[TOT]=x;
V[TOT]=y;
NXT[TOT]=FST[x];
FST[x]=TOT;
}
int p,tme,cnt=0;
void dfs(int x)
{
flag[x]=1;
struck[++head]=x;
dfn[x]=low[x]=++tme;
for(int i=fst[x];i;i=nxt[i])
{
if(!dfn[v[i]])
{
dfs(v[i]);
low[x]=min(low[x],low[v[i]]);
}
else if(flag[v[i]]) low[x]=min(low[x],dfn[v[i]]);
}
if(low[x]==dfn[x])
{
// cout<<endl<<x<<endl;
cnt++;
do{
Ans[cnt]=min(Ans[cnt],struck[head]);
flag[struck[head]]=0;
color[struck[head]]=cnt;
if(vis[struck[head]])
{
VIS[cnt]=1;
W[cnt]=min(W[cnt],w[struck[head]]);
}
head--;
}while(struck[head+1]!=x);
}
}
int main()
{
// freopen("in.txt","r",stdin);
cin>>n>>p;
for(int i=1;i<=n;i++) W[i]=2000000000,Ans[i]=2000000000;
for(int i=1;i<=p;i++)
{
int xxx;
cin>>xxx;
cin>>w[xxx];
vis[xxx]=1;
}
cin>>m;
for(int i=1;i<=m;i++)
{
int x,y;
cin>>x>>y;
add(x,y);
}
for(int i=1;i<=n;i++) if(!dfn[i]) dfs(i);
memset(flag,0,sizeof(flag));
for(int i=1;i<=m;i++)
{
if(color[u[i]]!=color[v[i]])
{
ADD(color[u[i]],color[v[i]]);
rd[color[v[i]]]++;
}
}
int num=cnt;
for(int i=1;i<=cnt;i++)
{
if(rd[i]==0)
{
sum++;
}
}
for(int i=1;i<=cnt;i++) if(rd[i]==0&&VIS[i]==1) sum--;
// for(int i=1;i<=cnt;i++) cout<<color[i]<<endl;
// cout<<endl;
// for(int i=1;i<=m;i++) cout<<U[i]<<"->"<<V[i]<<endl;
// cout<<endl;
if(sum!=0)
{
cout<<"NO"<<endl;
for(int i=1;i<=cnt;i++)
{
if(VIS[i])
{
q.push(i);
}
}
while(!q.empty())
{
int x=q.front();
q.pop();
flag[x]=1;
for(int i=FST[x];i;i=NXT[i])
{
q.push(V[i]);
}
}
// for(int i=1;i<=cnt;i++) cout<<flag[i]<<endl;
minn=2147483647;
for(int i=1;i<=cnt;i++) if(!flag[i]) minn=min(minn,Ans[i]);
cout<<minn;
}
else
{
sum=0;
for(int i=1;i<=cnt;i++) if(rd[i]==0) sum+=W[i];
cout<<"YES"<<endl<<sum;
}
}
luogu P1516 青蛙的约会
很有意思的一道数论,将环处理完其实就变成了不定方程求解,exgcd可解。
#include<bits/stdc++.h>
using namespace std;
long long x,y,m,n,l,d;
void exgcd(long long a,long long b)
{
if(b==0) x=1,y=0,d=a;
else{
exgcd(b,a%b);
long long t=x; x=y;y=t-a/b*y;
}
}
int main()
{
cin>>x>>y>>m>>n>>l;
long long A=n-m,B=x-y;
if(A<0) A=-A,B=-B;
exgcd(A,l);
if(B%d!=0) cout<<"Impossible";
else cout<<((x*(B/d))%(l/d)+(l/d))%(l/d);
}
luogu P1351 联合权值
前缀和容斥原理,暴力强无敌
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=500001;
const int P=10007;
int u[N],v[N],w[N],nxt[N],fst[N],tot,ans2;
int x,y;
int n,ans=0;
int flag,maxn,minn;
void add(int x,int y)
{
tot++;
u[tot]=x;
v[tot]=y;
nxt[tot]=fst[x];
fst[x]=tot;
}
signed main()
{
// freopen("in.txt","r",stdin);
scanf("%lld",&n);
for(int i=1;i<n;i++)
{
scanf("%lld%lld",&x,&y);
add(x,y);
add(y,x);
}
for(int i=1;i<=n;i++) scanf("%lld",&w[i]),w[v[i]]%=P;
for(x=1;x<=n;x++)
{
flag=maxn=minn=0;
for(int i=fst[x];i;i=nxt[i])
{
flag+=w[v[i]];
flag%=P;
if(w[v[i]]>=maxn) minn=maxn,maxn=w[v[i]];
else minn=max(minn,w[v[i]]);
}
for(int i=fst[x];i;i=nxt[i])
{
ans2+=w[v[i]]*(flag-w[v[i]]);
ans2%=P;
}
ans=max(ans,maxn*minn);
}
printf("%lld %lld",ans,((ans2%P)+P)%P);
}
luogu P2822 组合数问题
其实是一道裸的组合数取模,但是多组询问会让你每次o(nm)炸掉,前缀和做到o(1)查询即可。
#include<bits/stdc++.h>
using namespace std;
long long t,c[2001][2001],n,k,x,y,m,ans[2001][2001],ans2;
int main()
{
cin>>t>>k;
n=2000;
for(int i=0;i<=n;i++) c[i][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)
{
c[i][j]=c[i-1][j]+c[i-1][j-1];
c[i][j]%=k;
ans[i][j]=ans[i][j-1]+ans[i-1][min(i-1,j)]-ans[i-1][min(i-1,j-1)];
if(c[i][j]%k==0) ans[i][j]++;
}
}
for(int i=1;i<=t;i++)
{
ans2=0;
cin>>x>>y;
printf("%lld\n",ans[x][(min(x,y))]);
}
}
luogu P2513 [HAOI2009]逆序对数列&&P1521 求逆序对
dp加前缀和,其实o(n^3)也能卡过去,正解是o(nm)的,某队爷讲过一种快速路卡斯的方法跑的飞快,貌似是o(n根号n)的,但我不会,所以这里就放一下前缀和优化o(nm)的解法
#include<bits/stdc++.h>
using namespace std;
const int P=10000;
int a[1001][1001],sum,n,k;
int main()
{
cin>>n>>k;
if(k>(n-1)*n/2) {
cout<<0;
return 0;
}
a[1][0]=1;
for(register int i=2;i<=n;++i)
{
sum=0;
for(register int j=0;j<=k;++j)
{
sum+=a[i-1][j];
sum%=P;
a[i][j]=sum;
if(j-i+1>=0) sum-=a[i-1][j-i+1];
sum%=P;
sum=(sum+P)%P;
}
}
cout<<a[n][k];
}
luogu P2619 [国家集训队2]Tree I
很有意思的一道最小生成树题目,思路很奇妙,枚举一个值,每次为所有白边加上,跑最小生成树,根据白边数量修改当前值,可以二分枚举这个值,在mid>=need时更新ans。
#include<bits/stdc++.h>
using namespace std;
int n,m,l=-120,r=120,mid,temp,ans,fa[500001],need,sum,Ans;
struct EDG
{
int x,y,w,color;
}a[100001];
bool cmp(EDG a,EDG b){return (a.w==b.w)?(a.color<b.color):(a.w<b.w);};
int father(int x){return (x==fa[x])?(fa[x]):(fa[x]=father(fa[x]));};
int kelusikaer(int num)
{
for(int i=0;i<=n;i++) fa[i]=i;
temp=0,sum=0,ans=0;
for(int i=1;i<=m;i++) if(a[i].color==0) a[i].w+=num;
sort(a+1,a+m+1,cmp);
for(int i=1;i<=m;i++)
{
int fx=father(a[i].x),fy=father(a[i].y);
if(fy!=fx)
{
fa[fy]=fx;
sum++;
if(a[i].color==0) temp++;
ans+=a[i].w;
}
if(sum==n-1) break;
}
for(int i=1;i<=m;i++) if(a[i].color==0) a[i].w-=num;
return temp;
}
int main()
{
cin>>n>>m>>need;
for(int i=1;i<=m;i++) cin>>a[i].x>>a[i].y>>a[i].w>>a[i].color,a[i].x++,a[i].y++;
while(l<=r)
{
mid=(l+r)>>1;
if(kelusikaer(mid)>=need) l=mid+1,Ans=ans-mid*need;
else r=mid-1;
}
cout<<Ans;
}
luogu P2613 【模板】有理数取余
也是很有意思的一道题,通过转化为求逆元的方式来求解;注意数据范围略大,需要手写输入。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int x,y,d,a,b,inv[3000001];
int n,p=19260817;
int read()
{
char ch=getchar();
int x=0,k=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') k=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
x=(x<<3)+(x<<1)+ch-'0';
x%=p;
ch=getchar();
}
return x*k;
}
void exgcd(int a,int b)
{
if(b==0)
{
d=a;
x=1;
y=0;
}
else
{
exgcd(b,a%b);
int t=x;
x=y;
y=t-(a/b)*y;
}
}
signed main()
{
a=read();
b=read();
if(b==0)
{
cout<<"Angry!";
return 0;
}
exgcd(b,p);
x=((x%p)+p)%p;
cout<<(a*x)%p;
}
luogu P2512[HAOI 2008] 糖果传递
说实话我不会证明这个题。
详解见luogu第一篇题解?
#include<bits/stdc++.h>
using namespace std;
long long a[1000005],b[1000005],mid,ans,total,n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],total+=a[i];
total/=n;
for(int i=1;i<=n;i++) b[i]=b[i-1]-a[i]+total;
sort(b+1,b+1+n);
if(n%2) mid=b[n/2+1];
else mid=(b[n/2]+b[n/2+1])/2;
for(int i=1;i<=n;i++) ans+=abs(b[i]-mid);
cout<<ans;
}
P5431 【模板】乘法逆元2
很有意思的一道数论题,先通分,求前缀积,然后求两个逆元,即可解决问题,注意常数优化。。。
#include<cstdio>
#include<cctype>
#define int long long
using namespace std;
int n,k,p,a[5000001],mul=1,mul1[5000011],mul2=1,ans,kk=1,kkk=0,multy=1,niyuan;
inline int Pow(int a,int b)
{
int ans=1;
while(b)
{
if(b&1)
{
ans*=a;
ans%=p;
}
a*=a;
a%=p;
b>>=1;
}
return ans;
}
inline void read(int &x)
{
x = 0;
int k = 1;
char c = getchar();
while (!isdigit(c))
{
if (c == '-') k = -1;
c = getchar();
}
while (isdigit(c))
{
x = (x << 3) + (x << 1) + (c ^ 48);
c = getchar();
}
x *= k;
}
signed main()
{
// freopen("testdata.in","r",stdin);
read(n),read(p),read(k);
for(register int i=1;i<=n;i++) read(a[i]);
mul1[n+1]=1;
for(register int i=n;i>=1;i--)
{
mul1[i]=mul1[i+1]*a[i];
mul1[i]%=p;
}
for(register int i=1;i<=n;i++)
{
kk*=k;
kk%=p;
kkk+=(kk*((mul*mul1[i+1])%p));
kkk%=p;
mul*=a[i];
mul%=p;
}
niyuan=Pow(kkk,p-2);
niyuan=((niyuan%p)+p)%p;
mul=mul*niyuan%p;
printf("%lld",(Pow(mul,p-2)%p+p)%p);
}
luogu P2602 [ZJOI2010]数字计数
数位dp入门经典题目之一,这里用的是最常规的dfs解法(数位dp通解)。按照每一个数字分别进行dp统计并输出,记住这里sum状态记录的是操作数所以一定要记录下来。
(虽然不是很明白为啥一定要记录sum)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,num[201],dp[201][201],l,r,len;
int dfs(int now,int sum,bool limit,bool qiandaoling,int d)
{
int res=0;
if(now==0) return sum;
if(!limit&&qiandaoling&&dp[now][sum]) return dp[now][sum];
int maxn=(limit)?num[now]:9;
for(int i=0;i<=maxn;i++) res+=dfs(now-1,sum+((i==d)&&(i||qiandaoling)),(i==num[now])&&limit,(qiandaoling||i),d);
if(!limit&&qiandaoling) dp[now][sum]=res;
return res;
}
int work(int x,int flag)
{
len=0;
while(x)
{
num[++len]=x%10;
x/=10;
}
memset(dp,0,sizeof(dp));
return dfs(len,0,1,0,flag);
}
signed main()
{
cin>>l>>r;
for(int i=0;i<=9;i++) cout<<work(r,i)-work(l-1,i)<<" ";
}
luogu P5022 [NOIP2018] 旅行
首先我采用的是最好想,最常见的暴力删边方案
但是很显然,在luogu评测机上,这样的做法会TLE,因为常数很大,整体复杂度又是o(n方logn)的;
一开始我还坚定地认为只要卡常就能a掉这道题,然后我把用到的stl全手写了一遍,发现还是tle;
于是我开始思考如何剪枝。
第一时间,我想到在判断答案的时候每次看当前是否能更新最小字典序,但是在wa了4次之后我发现这样做不可行虽然我不知道为什么不对而且他依然是wa的
在付出了50多次惨痛的TLE,WA之后,我终于找到了合适的剪枝方案
具体的剪枝那两行我已经写在程序注释里面了,相信大家一看就能看懂,我就不多解释了。
这确确实实是能够ac的程序。
一次不行就多交几次
代码比较长,但是由于我很菜,思路写的极度清晰,所以这份代码应该普及选手都能看懂才对
#include<bits/stdc++.h>
const int N=10001;
struct NODE
{
int x,y;
}tree[10001];
inline int operator <(NODE a,NODE b)
{
if(a.y==b.y) return a.x<b.x;
return a.y>b.y;
}
int tt;
inline void swap(NODE &x,NODE &y)
{
NODE t=x;
x=y;
y=t;
}
namespace dui
{
inline void update(int p)
{
while(p>1)
{
if(tree[p]< tree[p>>1]) swap(tree[p],tree[p>>1]),p>>=1;
else break;
}
}
inline void push(NODE x)
{
tt++;
tree[tt]=x;
update(tt);
}
inline void downpdate(int x)
{
int s=x<<1;
while(s<=tt)
{
s+=(s<tt&&tree[s+1]<tree[s]);
if(tree[s]<tree[x]) swap(tree[x],tree[s]),x=s,s=x<<1;
else break;
}
}
inline void pop()
{
tree[1]=tree[tt];
tt--;
downpdate(1);
}
inline NODE top() {return tree[1];}
}
int u[N],v[N],w[N],fst[N],nxt[N],tot=0,ans[N];
int flag[N],flg[N];
inline void add(int a,int b)
{
tot++;
u[tot]=a;
v[tot]=b;
nxt[tot]=fst[a];
fst[a]=tot;
tot++;
u[tot]=b;
v[tot]=a;
nxt[tot]=fst[b];
fst[b]=tot;
}
int n,m;
int pre[N];
inline int check()
{
for(register int i=1;i<=n;i++)
if(!ans[i] || !pre[i]) return !ans[i]?true:false;
else if(ans[i]>pre[i]) return true;
else if(ans[i]<pre[i]) return false;
}
inline void gx()
{
for(register int i=1;i<=n;i++) ans[i]=pre[i];
}
inline int read()
{
char ch=getchar();
int x=0;
while(ch<'0'||ch>'9') ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x;
}
int main()
{
srand(time(0));
n=read(),m=read();
for(register int i=1;i<=m;i++) add(read(),read());
if(m==n)
{
for(register int k=1;k<=m*2;k+=2)
{
if(n==5000&&rand()%2) continue;//剪枝就在这里哦
/* 实际上会TLE的只有三个点
然而正解实际上只有一条边是被跳过的
我们没有办法在合适的复杂度内找到它
但是我们可以通过这种手段把它减掉
每次有1/2的概率剪枝成功
三个点也就是说1/8的概率可以ac这道题
*/
pre[1]=1;
flg[1]=k;
NODE a;
a.x=1;
a.y=1;
dui::push(a);
int num=0;
while(tt)
{
a=dui::top();
dui::pop();
int x=a.x;
num++;
pre[num]=x;
for(register int i=fst[x];i;i=nxt[i])
{
if((i==k||i==k+1)||(flg[v[i]]==k)) continue;
NODE aa;
aa.x=v[i];
aa.y=num;
dui::push(aa);
flg[v[i]]=k;
}
}
if(check()) gx();
}
for(int i=1;i<=n;i++) printf("%d ",ans[i]);
}
else
{
pre[1]=1;
flg[1]=1;
NODE a;
a.x=1;
a.y=1;
dui::push(a);
int num=0;
while(tt)
{
a=dui::top();
dui::pop();
int x=a.x;
num++;
pre[num]=x;
for(int i=fst[x];i;i=nxt[i])
{
if(!flg[v[i]])
{
NODE aa;
aa.x=v[i];
aa.y=num;
dui::push(aa);
flg[v[i]]=1;
}
}
}
for(int i=1;i<=n;i++) printf("%d ",pre[i]);;
}
}
未完待续。。。。