一、小凯的疑惑:
去年比赛时懵逼报零的题目,考完之后大家告诉我是小学奥数,直接ab-a-b就A了。之后去向别人请教,某些人说:这题就是一道打表题,直接打表就行了,或者看洛谷的题解上面大多都是在证明为什么ab-a-b是答案是正确的,可是我考虑的是,这个ab-a-b从何而来?题解大多数好像并没有提到,或者有的dalao写的exgcd我也不清楚是怎么求的,机房的学长去年有写exgcd的但是挂掉了。 今天,借助某位dalao的思路,我来为大家提供一种新的简单的思路(毕竟这是noip的第一题,不会那么难吧?)a与b是互质的,我们可以无限使用a和b来组成一些数,我们要求的是最大的那个不能有a和b组成的数。假设只有b,那么b可以组成的数一定是0,b,2b……那么a和b组合起来的作用在哪儿呢?学过同余的都知道,只使用b的时候,我们关于模b的剩余系中就只枚举出来了余数为0的情况。a的作用就是把它乘上某一个倍数,使它出现模b的另一个剩余系。我们发现,因为a,b互质,只要把a乘上b-1次,此时就已经出现了所有的剩余系。这时候到当前这个数,我们其实已经覆盖了模b的所有剩余系,所以它之后的所有数都可以由a,b组成。而对于之前的数:到a(b-1)这个数的时候我们刚好覆盖了所有的剩余系,设a(b-1)%b=r,那么说明在这之前模b余数为r的值是无法组成的,而从它到a(b-1)这中间的剩余系因为在之前已经被覆盖了,所以都可以组成。那么最大的不能组成的数就是a(b-1)-b,也就是a*b-a-b。
二、奶酪:
貌似思路很多?有人写的并查集,有人写的最短路好像,我是直接建图然后跑dfs。noip的时候也是这么写的,具体怎么挂成40了我也记不清楚了,总之是送分题。
#include
#define ll unsigned long long
using namespace std;
const int maxn=1e3+10;
int n,t,h,r,tot;bool flag;
int ver[maxn*maxn],Next[maxn*maxn],lin[maxn];
int x[maxn],y[maxn],z[maxn];bool v[maxn];
void add(int x,int y)
{
ver[++tot]=y;Next[tot]=lin[x];lin[x]=tot;
}
void dfs(int x)
{
if(x==n+1){
flag=1;return;}
for(int i=lin[x];i;i=Next[i]){
int y=ver[i];
if(!v[y]){
v[y]=1; dfs(y);
}
}
return ;
}
int main()
{
scanf("%d",&t);
while(t--){
memset(Next,0,sizeof(ver));
memset(lin,0,sizeof(lin));
scanf("%d%d%d",&n,&h,&r);
for(int i=1;i<=n;i++){
scanf("%d%d%d",&x[i],&y[i],&z[i]);
}
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++){
if(2*r>=sqrt(1.0*(x[i]-x[j])*(x[i]-x[j])+1.0*(y[i]-y[j])*(y[i]-y[j])+1.0*(z[i]-z[j])*(z[i]-z[j])))
add(i,j),add(j,i);
}
for(int i=1;i<=n;i++) if(r>=z[i]) add(0,i),add(i,0);
for(int i=1;i<=n;i++) if(z[i]+r>=h) add(i,n+1),add(n+1,i);
memset(v,0,sizeof(v));
v[0]=1;flag=0;
dfs(0);
if(flag) printf("Yes\n");
else printf("No\n");
}
return 0;
}
三、逛公园
设1到N的最短路为d,题目让求的就是,关于给定的图,从1到N路径长度不大于d+k的路径数。然后题目中可以存在零环。如果有无数解,就输出-1。
简单分析一下,我们要做的有两件事,一个是判断是否存在零环并且计算会不会对答案做出贡献,如果是,那么一定有无数组解,反之则零环对答案没有影响。 第二件事就是求路径数了。
30分暴力是k=0,并且没有零环,那我们只需要做最短路计数就行。
70分是没有零环的情况,我们考虑如何求解。其实看了一下数据,k不大,我们可以在图上做dp。设f【x】【i】表示从1走到x,路径长度为d【x】+i的方案数。每一个状态只能由它相邻的点更新,然后写递推式或者记忆化搜索就可以了。
100分的写法:其实我们可以第一件事就做零环,我们把所有的零环找出来,然后用并查集把每一个零环上的点合并起来成一个点,判断1到这个点的最短距离加上这个点到N的最短距离,是否小于等于d+k,如果有一个点符合条件,就输出-1.如果所有的都不符合,说明不存在无数解,就可以再开始做dp就可以了。当然其实如果你写的dp是记忆化搜索,不用写并查集那么麻烦。
#include
using namespace std;
const int N=1e5+10;
const int M=2e5+10;
int t,n,m,k,p,tot,ver[M],Next[M],lin[N],edge[M],d[N],v[N],vc[M],Nex[M],lc[N];
int f[N][60],flag[N][60];
void add(int x,int y,int z){
ver[++tot]=y;Next[tot]=lin[x];lin[x]=tot;edge[tot]=z;
vc[tot]=x;Nex[tot]=lc[y];lc[y]=tot;
}
void dijkstra(){
priority_queue >q;
memset(d,0x3f,sizeof(d));
d[1]=0;q.push(make_pair(0,1));
while(q.size()){
int x=q.top().second;q.pop();
if(v[x]) continue;
v[x]=1;
for(int i=lin[x];i;i=Next[i]){
int y=ver[i];
if(d[y]>d[x]+edge[i]){
d[y]=d[x]+edge[i];
q.push(make_pair(-d[y],y));
}
}
}
}
int dfs(int x,int l){
if(l<0||l>k) return 0;
if(flag[x][l]){
flag[x][l]=0;
return -1;
}
if(f[x][l]!=-1) return f[x][l];
int ans=0;
flag[x][l]=1;
for(int i=lc[x];i;i=Nex[i]){
int y=vc[i];
int val=dfs(y,d[x]+l-edge[i]-d[y]);
if(val==-1){
flag[x][l]=0;
return -1;
}
ans=(long long)(ans+val)%p;
}
flag[x][l]=0;
if(x==1&&l==0) ans++;
f[x][l]=ans;
return ans;
}
int solve(){
dijkstra();
int ans=0;
for(int i=0;i<=k;i++){
int val=dfs(n,i);
if(val==-1) return -1;
ans=(long long)(ans+val)%p;
}
return ans;
}
int main(){
scanf("%d",&t);
while(t--){
tot=0;
scanf("%d%d%d%d",&n,&m,&k,&p);
for(int i=1;i<=n;i++) lin[i]=lc[i]=v[i]=0;
for(int i=1;i<=m;i++) Nex[i]=Next[i]=edge[i]=0;
memset(f,-1,sizeof(f));
memset(flag,0,sizeof(flag));
for(int i=1;i<=m;i++){
int x,y,z;scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
}
printf("%d\n",solve());
}
}
四、时间复杂度
这道题就是栈的纯模拟,就是处理的时候麻烦了一些。
我们发现,在不考虑ERR的情况时,所有的结构都是循环,无非有嵌套,嵌套里还会有并列的,纯模拟计算时间复杂度比较麻烦,但是我们发现每次遇到一个E我们肯定要计算之前那个F的复杂度然后看它对答案的贡献的,一进一出,所以我们要用栈。
每次遇到一次F,就算一下它的复杂度,然后把它入栈。如果遇到E,就把当前栈顶出栈,并且拿它来更新新的栈顶。按照这种思路写,你每次出栈之后新的栈顶一定是刚出栈的那个循环上面套的循环,所以更新答案时应该是两个复杂度相加。
大致思路就是如此,具体细节自己考虑一下。
其实你发现,ERR无非两种情况:
(1)变量名重复定义 (2)开始循环的F与结束循环的E对不上
第二种情况在做栈的时候就可以判断,如果当前top为0,但仍然要出栈,就输出ERR。
第一种情况可以在记录栈的同时,记录一下变量名,用map处理。每次入栈的时候,判断变量名是否使用,如果使用了就输出ERR,否则就把变量名加入map,并且记录一下栈中每个元素对应的变量名,在出栈的时候把map中的变量名再删去。
#include
#include
#include
#include
五、宝藏
n<=12,显然是搜索或者状压。我们可以枚举每一个点作为起点,然后dfs,递归传递的是每一个二进制压缩的状态,再枚举每一个点作为当前所在点,然后再枚举要到达的点,更新最优值。稍微做两个剪枝,跑的很快。(也可能是数据水?)
#include
#include
#include
#include
using namespace std;
#define INF 2147483647
int n,m,g[20][20],f[10000],dis[20],ans=INF;
void find(int x)
{
if(f[x]>ans) return ;
for(int i=1;i<=n;i++)
if((1<<(i-1))&x)
{
for(int j=1;j<=n;j++)
if(((1<<(j-1))&x)==0&&g[i][j]!=INF)
{
if(f[x|(1<<(j-1))]>f[x]+dis[i]*g[i][j])
{
int tmp=dis[j];
dis[j]=dis[i]+1;
f[x|(1<<(j-1))]=f[x]+dis[i]*g[i][j];
find(x|(1<<(j-1)));
dis[j]=tmp;
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
g[i][j]=INF;
int u,v,z;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&u,&v,&z);
g[u][v]=min(g[u][v],z);
g[v][u]=min(g[v][u],z);
}
for(int o=1;o<=n;o++)
{
for(int i=1;i<=n;i++) dis[i]=INF;
for(int i=1;i<=(1<
六、列队
我们发现,每一次出去一个人之后,改变的位置只有当前所在行,以及最后一列。考虑我们其实每次修改一次,可以把原来的那个点删除,然后分别在行、列的末尾新加入一个点,然后每次查询我们要做的是求区间第k个数。这个可以用线段树实现。我们开n+1棵线段树,前n个分别维护的是矩阵的n行的前m-1的人编号的情况,第n+1棵维护的是最后一列的人的编号情况。每次要求x,y位置人的编号,如果y=m,我们就是在第n+1棵线段树上找第x个数的val。否则就是在第x棵线段树上找第y个数的val;修改类似。当然,开n+1棵线段树,复杂度肯定爆炸啊,所以我们要动态开点。首先,线段树维护的区间长度是多少呢?我们发现,初始长度为max(n,m),最多加了q个点,那么最大长度p就是max(n,m)+q;一般动态开点,线段树的空间要p*20.不过已经符合了本题的要求。
#include
#define ll long long
using namespace std;
const int maxn=3e5+10;
int n,m,q,p,tot,now;
int ls[maxn*20],rs[maxn*20],root[maxn],siz[maxn*20],pos[maxn];
ll val[maxn*20];
int read(){
char ch=getchar();int num=0,f=1;
while(!isdigit(ch)){if(ch=='-') f=-1; ch=getchar();}
while(isdigit(ch)){num=num*10+ch-'0'; ch=getchar();}
return num*f;
}
void init(){
n=read();m=read();q=read();
}
int calc(int l,int r){//计算这一段区间初始有多少个数
if(now==n+1){
if(r<=n) return r-l+1;
else if(l<=n) return n-l+1;
else return 0;
}
if(r>1;
if(!ls[id]&&mid-l+1>=x) return query(ls[id],l,mid,x);
if(ls[id]&&siz[ls[id]]>=x) return query(ls[id],l,mid,x);
if(ls[id]) x-=siz[ls[id]];
else x-=mid-l+1;
return query(rs[id],mid+1,r,x);
}
void update(int &id,int l,int r,ll temp,int x){
if(!id){
id=++tot;
siz[id]=calc(l,r);
if(l==r){
val[id]=temp;
}
}
siz[id]++;//新增一个节点
if(l==r) return ;
int mid=l+r>>1;
if(mid>=x) update(ls[id],l,mid,temp,x);
else update(rs[id],mid+1,r,temp,x);
}
void work(){
ll ans;p=max(m,n)+q;
for(int i=1;i<=q;i++){
int x,y;scanf("%d%d",&x,&y);
now=x;
if(y==m){
now=n+1;
ans=query(root[now],1,p,x);
}else
ans=query(root[now],1,p,y);
printf("%lld\n",ans);
now=n+1;pos[now]++;
update(root[now],1,p,ans,n+pos[now]);
if(y^m){
ans=query(root[now],1,p,x);
now=x;++pos[now];
update(root[now],1,p,ans,m-1+pos[now]);
}
}
}
int main(){
init();
work();
return 0;
}