PKU Helper 2017 开发组招新题目解答

PKU Helper 2017 开发组招新题目解答

T1

请自己实现一个 sqrt 函数,接受一个浮点型变量,返回其算数平方根,返回结果在 1e-6 的误差范围内相等即可。不得调用语言原生的 sqrt 库函数。

使用牛顿迭代法来实现sqrt函数,相比一般的二分查找办法效率更高,其基本思想是给出方程x^2=a的解;为此,在x轴上任取一点,作y轴的平行线,在与函数图像的交点作函数图像切线,得到与x轴的交点从而得到新的x值,重复上述步骤直到误差小于1e-6。

代码如下:

#include <iostream>
using namespace std;

double sqrt(double x) {
    if (x < 0) {
        cout << "require an input that greater than zero";
        return -1;
    }
    double result = x + 0.5;//取初值
    while (true) {          //迭代
        double temp = result - 1e-6;
        if (temp*temp < x)break;
        result = (result + x / result) / 2;
    }
    return result;
}
int main() {
    double a;
    cin >> a;
    cout << sqrt(a);
    return 0;
}

这里使用初值x+0.5的原因是保证第一次得到的交点位于x轴上方,从而保证每次迭代得到的函数值都大于0,这样在判断是否得到满足精度条件的结果时,只需简单地判断result - 1e-6这个值所对应函数值是否小于0。

结果符合精度。


T2

请通过 HTTP 抓包、Chrome 调试模式、查阅 HTML 源码或其他任何合理方式(甚至是搜索前人的博客或反编译 PKU Helper 应用等),分析北大门户(portal.pku.edu.cn)、网关(its.pku.edu.cn)、选课网(elective.pku.edu.cn)或教务网(dean.pku.edu.cn)其中任何一个或多个的登录行为。并根据你的分析结果,通过合适的顺序调用校方接口(IAAA、portal 等),实现一个输入学号、密码,并返回对应的姓名的功能。

如果你有 Android、iOS 或 Web 开发能力的话,建议实现为 GUI 形式,包含简单的两栏输入和登录按钮,并在登录成功后显示姓名即可。

如果你无法实现为 GUI 形式,则实现为命令行输入用户名、密码,命令行输出姓名的形式即可。或者任何你认为的可以方便展示的形式。
一、登录行为分析

本次主要分析了在portal、its两网站上的登录行为。

因为比较简单,所以首先说明its的登录行为分析,分析的方法是使用fiddler对pku helper应用的登录行为进行抓包。其行为比较简单,向 https://its.pku.edu.cn:5428/ipgatewayofpku post具有x-www-form-urlencoded格式的实体,其中:

  • uid为校内用户名(即学号)
  • password是校园密码
  • operation是行为代码,如connect代表连接网关、disconnect代表断开连接、’disconnectall’代表断开全部连接
  • range在行为为connect时有效,其中range为1代表连接收费地址、range为2代表连接免费地址
  • timeout,衡为-1即可,应该是连接超时选项

接着分析在portal上的登录行为,该行为比较复杂,因为其还使用iaaa进行校内登录,本次分析主要使用阅读portal和iaaa两个网站源码的形式,同时还使用了chrome的调试模式对登录行为进行确认;登录行为如下:

  1. 用户进入portal首页,并点击登录按钮。
  2. 用户将被导航至iaaa页面进行iaaa登录,输入用户名和密码后,浏览器将向 https://iaaa.pku.edu.cn/iaaa/oauthlogin.do post具有x-www-form-urlencoded格式的实体,实体中比较重要的属性有:appid,代表委托iaaa进行登录的是何种应用,在portal语境下,这个值是portaluser_name,用户名(即学号);password,即密码;redirUrl,即在iaaa登录成功后需要跳转的页面,在portal语境下,这个url是 http://portal.pku.edu.cn/portal2013/ssoLogin.do ,用来做登录成功后的验证和跳转等善后工作。
  3. 若用户名密码无误,iaaa会返回形如{\"success\":true,\"token\":\"a754f5394819f1f50201d4aebb67a3c9\"}的json格式数据;其中,success属性值自然是指示登录是否成功,若成功,json中还会包含一个名为token的字符串格式数据,使用此token即可验证登录成功。登录成功后,iaaa接下来会跳转到第二步中redirUrl所指示的url,并且会在url上加入两个querystring作额外信息,分别是:rand,是一个0-1之间的随机双精度浮点数;token,即刚才返回的token,用于验证登录成功。
  4. 在跳转页面中,会进行登录验证的操作,然后自动跳转到已登录状态的portal首页。这里需要注意的是,没有token在querystring中的情况或是跳转时发送的get请求头中没有一开始portal set的cookie的情况都会导致报错,因此在用代码模拟登录行为时不能跳过get portal首页的步骤而直接模拟iaaa登录,因为首先需要得到portal的cookie。
  5. 登录成功后就可以获取用户的真实姓名了,我这里使用的是在Portal的js文件index.js中找到的接口 /portal2013/isUserLogged.do ;直接get此接口就可以得到包含用户真实姓名的json格式数据。同样,未登录和没有cookie的情况都会导致报错。

上述就是portal登录的主要步骤,其实elective的登录流程是十分类似的,因为其采用的形式也是委托iaaa进行登录、并带着token跳转回来,在这里就不再阐述了;由于时间关系,dean的登录行为就没有作分析,故也不再阐述了。分析的过程中主要参考了portal和iaaa网站上的两段js代码,两段比较重要的函数粘贴如下以供参考:

1.iaaa的主要登录行为函数oauthLogon,位于iaaa网站的OAuthLogin.js文件中

function oauthLogon () {
   if($("#user_name").val()=="" || $("#user_name").val()=="学号/职工号/北大邮箱") { 
        //$("#msg").text("账号不能为空");
        $("#msg").html("<i class=\"fa fa-minus-circle\"></i> 账号不能为空");
        $("#user_name").focus();
   }else if($("#password").val()=="" ||$("#password").val()=="密码") { 
        //$("#msg").text("密码不能为空");
        $("#msg").html("<i class=\"fa fa-minus-circle\"></i> 密码不能为空");
        $("#password").focus();
   }
   else if($("#otp_area:visible").length>0 && 
    ($("#otp_code").val()==""  ||	$("#otp_code").val()=="动态口令")) { 
        $("#msg").html("<i class=\"fa fa-minus-circle\"></i> 动态口令不能为空");
        $("#otp_code").focus();
   }
   else if($("#sms_area:visible").length>0 && 
    ($("#sms_code").val()==""  ||	$("#sms_code").val()=="短信验证码")) { 
        $("#msg").html("<i class=\"fa fa-minus-circle\"></i> 短信验证码不能为空");
        $("#sms_code").focus();
   }
   else if($("#code_area:visible").length>0 && 
    ($("#valid_code").val()==""  ||	$("#valid_code").val()=="验证码")) { 
        //$("#msg").text("验证码不能为空");
        $("#msg").html("<i class=\"fa fa-minus-circle\"></i> 验证码不能为空");
        $("#valid_code").focus();
   }
   else { //document.myForm.submit();
        if($("#remember_check")[0].checked==true){
            setCookie("userName",$("#user_name").val());
            setCookie("remember","true");
        }
        else{
            delCookie("userName");
            delCookie("remember");
        }
        $("#msg").text("正在登录...");
        $.ajax('/iaaa/oauthlogin.do',
            {
                type:"POST",
                data:{appid: $("#appid").val(),
                    userName: $("#user_name").val(),
                    password: $("#password").val(),
                    randCode: $("#valid_code").val(),
                    smsCode:$("#sms_code").val(),
                    otpCode:$("#otp_code").val(),
                    redirUrl:redirectURL
                },
                dataType:"json",
                success : function(data,status,xhr) {
                    var json = data;
                    if(true == json.success){
                        //如果是弱口令 显示#msg 提示
                        if(json.isFlag){
                            $("#msg").text("密码强度不足,请尽快登录门户修改");
                            setTimeout(function(){
                                if(redirectURL.indexOf("?")>0)
                                window.location.href = redirectURL+"&rand="+Math.random()+"&token="+json.token;
                            else
                                window.location.href = redirectURL+"?rand="+Math.random()+"&token="+json.token;
                            },2000);
                        }else{
                            if(redirectURL.indexOf("?")>0)
                                window.location.href = redirectURL+"&rand="+Math.random()+"&token="+json.token;
                            else
                                window.location.href = redirectURL+"?rand="+Math.random()+"&token="+json.token;
                        }
                    }
                    else{
                        $("#msg").html("<i class=\"fa fa-minus-circle\"></i> "+json.errors.msg);
                        $("#code_img")[0].src="/iaaa/servlet/DrawServlet?Rand="+Math.random();
                        if("账号未激活"==json.errors.msg){
                            window.location.href = "https://iaaa.pku.edu.cn/iaaa/activateAccount.jsp?Rand="+Math.random()+"&activeCode="+json.activeCode;
                        }
                        else if("用户名错误"==json.errors.msg){
                            $("#user_name").select();
                            if(true==json.showCode){
                                $("#code_area").show();
                            }
                        }
                        else if("密码错误"==json.errors.msg){
                            $("#password").select();
                        }
                        else if("验证码错误"==json.errors.msg){
                            $("#code_area").show();
                            $("#valid_code").select();
                        }
                        else if("短信验证码错误或已过期"==json.errors.msg){
                            $("#sms_code").select();
                        }
                        else if("动态口令错误或已过期"==json.errors.msg){
                            $("#otp_code").select();
                        }
                    }
                },
                error : function(xhr,status,error) {
                    $("#msg").html("<i class=\"fa fa-minus-circle\"></i> 查询时出现异常");
                    $("#code_img").attr("src","/iaaa/servlet/DrawServlet?Rand="+Math.random());
                }
            });
   }
}

此函数承担了上述登录步骤中的第2、3步操作。
2.portal获取用户是否登录已经用户真名信息的函数isLogin,位于portal首页引用的js文件Index.js中:

function isLogin(){
    $.ajax("/portal2013/isUserLogged.do",{
        method:"POST",
        dataType:"json",
        error:function(xhr,status,error){
            $("#login").text("您好,请登录");
            $("#login").attr("href","/portal2013/login.jsp");
            $("#login").css("width","120px");
        },
        success:function(data,status,xhr){
            if(data.success==true){
                $("#login").text("您好,"+data.userName+" [退出]");
                $("#login").attr("href","/portal2013/logout.do");
                $("#login").css("width","160px");
                var candidate=["李庭晏","陈光","欧阳荣彬","龙新征","王倩宜","李丽","刘云峰","贾晓纯"];
                if(candidate.indexOf(data.userName)>=0){
                    $("#login").after("<a href=\"/portal2013/portal.jsp\">我的门户</a><a href=\"/portal2013/mobile/mp.html\">简约版</a>");
                }
                else
                    $("#login").after("<a href=\"/portal2013/portal.jsp\">我的门户</a>");
                /* $("div.menu").append('<a href="/portal2013/util/appSysRedir.do?appId=xjh" target="_blank" style="background-color:#dda813;">肖家河</a>');*/
            }
            else{
                $("#login").text("您好,请登录");
                $("#login").attr("href","/portal2013/login.jsp");
                $("#login").css("width","120px");
            }
        }
    });
}

此函数主要承担了上述第5步操作。

二、姓名查询功能实现

本次对于要求的姓名查询功能,主要使用了portal和iaaa提供的接口,所使用接口的详情与登录查询步骤都已经在上一节对于portal登录行为的分析中详细阐述了,在这一节中只需要用代码模拟此登录过程即可。

首先看结果,我所选用的方法是开发一个简单的android应用;按照要求,其界面包含简单的两栏输入和登录按钮,并在登录成功之后会调用查询姓名接口,并将用户姓名展示在按钮下方。

预览图如下:
helper_test2

这个应用就是直接在android studio默认模板上改的,所以还留着默认模板的影子,美观程度非常一般,不过所要求的功能已经完整实现了。

项目的完整代码我已经上传至我的github仓库,下面介绍一下项目的主要代码:

content_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    app:layout_behavior="@string/appbar_scrolling_view_behavior"
    tools:context="com.example.asus.yh1.MainActivity"
    tools:showIn="@layout/activity_main">


    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/loginstr"
        android:id="@+id/btn1"
        android:nestedScrollingEnabled="false"
        android:onClick="fuc"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="188dp"
        android:layout_alignRight="@+id/txtPswd"
        android:layout_alignEnd="@+id/txtPswd"
        android:layout_alignLeft="@+id/txtPswd"
        android:layout_alignStart="@+id/txtPswd" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="textPassword"
        android:ems="10"
        android:id="@+id/txtPswd"
        android:layout_above="@+id/btn1"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="30dp"
        android:hint="密码" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="number"
        android:ems="10"
        android:id="@+id/txtUserName"
        android:layout_centerHorizontal="true"
        android:layout_above="@+id/txtPswd"
        android:layout_marginBottom="30dp"
        android:hint="用户名" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="预定显示姓名"
        android:id="@+id/rNameView"
        android:layout_alignTop="@+id/btn1"
        android:layout_centerHorizontal="true"
        android:layout_below="@id/btn1"
        android:layout_marginTop="60dp"/>


</RelativeLayout>

这是应用程序的主界面设计,可以看到布局非常简单,在一个RelativeLayout中依次放入要求的组件即可,这里可以看到为按钮btn1绑定了一个名为fuc的onClick函数。

MainActivity.java

package com.example.asus.yh1;

import android.os.Handler;
import android.os.Message;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import org.json.*;

import com.example.asus.yh1.lib.Parameter;
import com.example.asus.yh1.lib.WebConnection;

import java.util.ArrayList;
import java.util.Random;


public class MainActivity extends AppCompatActivity {
    public static final int SHOW_NAME=0;
    public static final int SHOW_ERROR=1;

    private Handler handler=new Handler(){
        public void handleMessage(Message msg){
            switch (msg.what){
                case SHOW_NAME:
                {
                    String name=(String)msg.obj;
                    TextView rNameView=(TextView)findViewById(R.id.rNameView);
                    rNameView.setText("你的真名就是"+name);
                }break;
                case SHOW_ERROR:{
                    String errorMsg=(String)msg.obj;
                    Toast.makeText(getApplicationContext(), errorMsg, Toast.LENGTH_SHORT).show();
                }
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });
    }

    public void fuc(View v){
        EditText txtUser=(EditText)findViewById(R.id.txtUserName);
        EditText txtPswd=(EditText)findViewById(R.id.txtPswd);
        String userName=txtUser.getText().toString();
        String pswd=txtPswd.getText().toString();

        if(userName==""||pswd==""||userName==null||pswd==null){
            Toast.makeText(getApplicationContext(), "用户名或密码不能为空", Toast.LENGTH_SHORT).show();
            return;
        }

        doLogin(userName,pswd);

        return;
    }

    private void doLogin(String userName,String pswd){
        final String n=userName;
        final String p=pswd;
        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    WebConnection.connectWithGet("http://portal.pku.edu.cn/portal2013/index.jsp");
                    ArrayList<Parameter> list=new ArrayList<>();
                    list.add(new Parameter("appid", "portal"));
                    list.add(new Parameter("userName",n));
                    list.add(new Parameter("password", p));
                    list.add(new Parameter("redirUrl",
                            "http://portal.pku.edu.cn/portal2013/login.jsp/../ssoLogin.do"));
                    Parameter rt1=WebConnection.connectWithPost("https://iaaa.pku.edu.cn/iaaa/oauthlogin.do", list);
                    if(!"200".equals(rt1.name)){
                        throw new Exception("登录iaaa失败!");
                    }
                    JSONObject json=new JSONObject(rt1.value);
                    Boolean success=json.optBoolean("success");
                    String token=json.optString("token");
                    Random ran=new Random();
                    WebConnection.connectWithGet("http://portal.pku.edu.cn/portal2013/ssoLogin.do?rand="+ran.nextDouble()+"&token="+token);
                    Parameter rt2=WebConnection.connectWithGet("http://portal.pku.edu.cn/portal2013/isUserLogged.do");
                    if(!"200".equals(rt2.name)){
                        throw new Exception("获取姓名失败!");
                    }
                    JSONObject json2=new JSONObject(rt2.value);
                    success=json2.optBoolean("success");
                    if(!success){
                        throw new Exception("获取姓名失败,没能成功登录portal!");
                    }
                    String realName=json2.optString("userName");
                    Message message=new Message();
                    message.what=SHOW_NAME;
                    message.obj=realName;
                    handler.sendMessage(message);
                }catch (Exception e){
                    Message message=new Message();
                    message.what=SHOW_ERROR;
                    message.obj=e.getMessage();
                    handler.sendMessage(message);
                }
            }
        }).start();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

这里是项目主activity的逻辑(实际上就这一个activity emmm),可以看到在fuc函数中主要做了对输入的检查和转换,而主要的登录逻辑都是在函数doLogin中完成的。在该函数中,首先另开一线程以进行网络连接,接下来使用Parameter和WebConnection这两个我在项目中自定义的类对上一节中提到的登录过程进行模拟,也就是一系列按照时间顺序发送的post和get请求而已,很好理解,最后对返回的json数据进行解析,交给handler将数据刷新到界面上即可。

Parameter.java

package com.example.asus.yh1.lib;

/**
 * Created by asus on 2017/10/12 0012.
 */
public class Parameter {
    public String name;
    public String value;
    public Parameter(String n,String v){
        name=n;
        value=v;
    }
}

Parameter类的用途主要有两个:为代码的post方法提供post实体和暂时存储服务器返回的信息。可以看到Parameter类实际就是一个简单的键值对。作post实体时,其键值对的形式很自然地就和x-www-form-urlencoded形式的post实体内容对应起来;而存储response返回数据时,在name部分存储返回代码,而在value部分存储返回数据即可。

Webconnction.java

package com.example.asus.yh1.lib;

import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.params.ConnRoutePNames;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.*;

/**
 * Created by asus on 2017/10/12 0012.
 */
public class WebConnection {
    public static Parameter connectWithPost(String url,ArrayList<Parameter> params){
        if (params == null || params.size() == 0) {
            return connectWithGet(url);
        }
        try {
            url = url.trim();
            HttpParams httpParams = new BasicHttpParams();

            DefaultHttpClient httpClient = HTTPSClient.getHttpClient(httpParams);
            HttpPost httpPost = new HttpPost(url);


            List<NameValuePair> pList = new ArrayList<NameValuePair>();
            if (params != null) {
                for (Parameter paraItem : params) {
                    String string = paraItem.value;
                    if (string == null || "".equals(string)) continue;
                    pList.add(new BasicNameValuePair(paraItem.name, paraItem.value));
                }
            }
            Cookies.addCookie(httpPost);
            httpPost.setHeader(HTTP.CONTENT_TYPE, "application/x-www-form-urlencoded");
            if (pList.size() != 0)
                httpPost.setEntity(new UrlEncodedFormEntity(pList, "utf-8"));

            HttpResponse httpResponse = httpClient.execute(httpPost);

            int returncode = httpResponse.getStatusLine().getStatusCode();
            Cookies.setCookie(httpResponse, url);
            String value = "";
            if (returncode == 200) {
                BufferedReader bf;
                bf = new BufferedReader(
                        new InputStreamReader(httpResponse.getEntity().getContent()));


                String line = bf.readLine();
                while (line != null) {
                    value += line + "\n";
                    line = bf.readLine();
                }
                value = value.trim();
            }
            Parameter parameters = new Parameter(returncode+"", value);
            return parameters;
        } catch (Exception e) {
            e.printStackTrace();
            return new Parameter("-1", "");
        }
    }
    public static Parameter connectWithGet(String url) {
        try {
            url = url.trim();
            HttpParams httpParams = new BasicHttpParams();

            DefaultHttpClient httpClient = HTTPSClient.getHttpClient(httpParams);
            HttpGet httpGet = new HttpGet(url);
            Cookies.addCookie(httpGet);
            HttpResponse httpResponse = httpClient.execute(httpGet);


            int returncode = httpResponse.getStatusLine().getStatusCode();

            Cookies.setCookie(httpResponse, url);
            String value = "";
            if (returncode == 200) {
                BufferedReader bf;
                    bf = new BufferedReader(
                            new InputStreamReader(httpResponse.getEntity().getContent()));


                String line = bf.readLine();
                while (line != null) {
                    value += line + "\n";
                    line = bf.readLine();
                }
            }
            return new Parameter(returncode+"", value);
        } catch (Exception e) {
            return new Parameter("-1", "");
        }
    }
}

WebConnction是一个用于进行web连接的工具类,其一共包含两个静态方法:connectWithGet和connectWithPost,其中connectWithGet可以向指定的url发送get请求,而connectWithPost则接受一个Parameter的ArrayList,并将ArrayList中的数据转换成为x-www-form-urlencoded形式的post实体并向指定url发送post请求。二者都是对android自带的HttpClient的简单封装,不再赘述。

上述用于web连接的工具类很大程度上都借鉴了PKU helper Android的写法,将web连接包装为单独的工具类自不用说,将数据包装为Parameter的形式的确十分简洁而且泛用性很高(因为实际的应用中很多时候都会使用键值对这一形式);我在拜读源码的时候深受启发。


T3

Google Kickstart Round E:完成其中任何一道题目即可。

emmmmmm,目前来说,我不能说自己完成了其中任何一道题目,所以只能拿两道半成的题目来凑了=——=。

一、Copy & Paste

这道题魔性的地方在于在复制之后剪贴板的内容在再次复制之前都会保留,而且复制竟然还算一步操作,这就让我之前动态规划的想法不知道怎么施展了;最后给出的解法是使用广搜。代码如下:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <ostream>
#include <fstream>
#include <string>
#include <queue>
using namespace std;
string str;
int strsz;
struct rec {
    int step;
    int count;
    int clipHead;
    int clipEnd;
};
bool myCmp(int f1,int e1,int f2,int e2) {
    int len1 = e1 - f1, len2 = e2 - f2;
    if (e1 > strsz || e2 > strsz) return false;
    if (len1 <= 0 || len2 <= 0 || len1 != len2) {
        return false;
    }
    for (int i = 0; i < len1; i++) {
        if (str[f1 + i] != str[f2 + i]) return false;
    }
    return true;
}

int main() {

    int T,N;
    ifstream cin("C:\\Users\\yhang\\Documents\\Visual Studio 2017\\Projects\\ConsoleApplication1\\A-small-practice.in");
    ofstream of("out.txt");
    streambuf* fileBuf = of.rdbuf();
    cout.rdbuf(fileBuf);
    cin >> T;
    N = T;
    while (T--) {
        cin >> str;
        strsz = str.size();
        queue<rec> records;
        rec first;
        first.count = 1;
        first.step = 1;
        first.clipHead = 0;
        first.clipEnd = 0;
        records.push(first);
        while (!records.empty()) {
            rec temp = records.front();
            records.pop();
            if (temp.count == strsz) {
                int result = temp.step;
                cout << "Case #" << N - T << ": " << result << endl;
                break;
            }
            rec add;
            add.count = temp.count + 1;
            add.step = temp.step + 1;
            add.clipHead = temp.clipHead;
            add.clipEnd = temp.clipEnd;
            records.push(add);
            for (int i = 0; i < temp.count; i++) {
                for (int j = i + 1; j <= temp.count; j++) {
                    string tempsub = str.substr(i, j - i);
                    if (str.rfind(tempsub) <= i)continue;
                    rec clip;
                    clip.count = temp.count;
                    clip.step = temp.step + 1;
                    clip.clipHead = i;
                    clip.clipEnd = j;
                    records.push(clip);
                }
            }
            if (myCmp(temp.clipHead, temp.clipEnd, temp.count, temp.count + temp.clipEnd - temp.clipHead)) {
                rec paste;
                paste.count = temp.count + temp.clipEnd - temp.clipHead;
                paste.step = temp.step + 1;
                paste.clipHead = temp.clipHead;
                paste.clipEnd = temp.clipEnd;
                if (paste.count == strsz) {
                    int result = paste.step;
                    cout << "Case #" << N - T << ": " << result << endl;
                    break;
                }
                records.push(paste);
            }
        }
    }
    return 0;
}

可以看到我使用一个自定义的结构体struct rec去记录进行每一步操作的状态,包括当前共使用了几步操作,已经形成了目标字符串的几个字符,以及当前剪贴板的内容。接下来使用通常的广搜步骤,维护一个rec的队列,每次从队列头取出状态并生成新的状态压入队列,最终找到第一个完全形成目标字符串的状态即为最优解。

为了增加效率,我还做了两步剪枝优化:一个是在生成新状态时就判断其是否满足了目标串条件,这样该状态就不用再加入队列,直接得到最优解;一个是在模拟复制操作时,判断当前复制的子串是否在之后的字符串中出现,若没有出现,那么复制当前的子串是无用的操作,不再生成新的状态。

然而剪完了还是没能过得了A-large-practice.in输入集=——=。

二、Trapezoid Counting

这道题我使用了一个带有优化的枚举策略。一般的想法是枚举输入中所有四条边的情况,我的枚举方法是首先找出输入中所有边长相等的边对,再以此为基础为所有的边对去寻找满足等腰梯形条件的另外两条边。

这个做法虽然时间复杂度还是O(N^4),但是考虑到实际的任务是寻找等腰梯形和实际的输入,平均复杂度应该不会达到最坏情况。代码如下:

#include <iostream>
#include <fstream>
#include <vector>
#include <set>
using namespace std;
struct trapezoid {
    int code1, code2, code3, code4;
    trapezoid(int c1, int c2, int c3, int c4) {
        int temp[4]{ c1,c2,c3,c4 };
        for (int i = 0; i < 3; i++) {
            for (int j = i+1; j < 4; j++) {
                if (temp[j] < temp[i]) {
                    int t = temp[i];
                    temp[i] = temp[j];
                    temp[j] = t;
                }
            }
        }
        code1 = temp[0];
        code2 = temp[1];
        code3 = temp[2];
        code4 = temp[3];
    }
    bool operator == (const trapezoid a) const {
        return this->code1 == a.code1&&this->code2 == a.code2&&this->code3 == a.code3&&this->code4 == a.code4;
    }
    bool operator < (const trapezoid a) const {
        if (this->code1 > a.code1) return false;
        else if (this->code1 < a.code1) return true;
        if (this->code2 > a.code2) return false;
        else if (this->code2 < a.code2) return true;
        if (this->code3 > a.code3) return false;
        else if (this->code3 < a.code3) return true;
        if (this->code4 > a.code4) return false;
        else if (this->code4 < a.code4) return true;
        return false;
    }
    bool operator > (const trapezoid a)const {
        return !(*this < a) && !(*this == a);
    }
};
struct sidepair {
    int code1, code2;
    long long len;
};
set<trapezoid> zoids;
vector<sidepair> pairs;
long long sides[5000];
int N;
int main() {
    int T;
    ifstream cin("C:\\Users\\yhang\\Documents\\Visual Studio 2017\\Projects\\ConsoleApplication2\\B-small-practice.in");
    ofstream of("out.txt");
    streambuf* fileBuf = of.rdbuf();
    cout.rdbuf(fileBuf);
    cin >> T;
    for (int i = 1; i <= T; i++) {
        zoids.clear();
        pairs.clear();
        cin >> N;
        for (int j = 0; j < N; j++) {
            cin >> sides[j];
        }
        for (int m = 0; m < N - 1; m++) {
            for (int n = m + 1; n < N; n++) {
                if (sides[m] == sides[n]) {
                    sidepair sp;
                    sp.code1 = m;
                    sp.code2 = n;
                    sp.len = sides[m];
                    pairs.push_back(sp);
                }
            }
        }
        int sz = pairs.size();
        for (int k = 0; k < sz; k++) {
            sidepair pr = pairs[k];
            int c1 = pr.code1;
            int c2 = pr.code2;
            long long clen = pr.len;
            for (int m = 0; m < N - 1; m++) {
                if (m == c1 || m == c2) continue;
                for (int n = m + 1; n < N; n++) {
                    if (n == c1 || n == c2 || n == m) continue;
                    if (sides[m] == sides[n]) continue;
                    long long l1 = sides[m], l2 = sides[n];
                    if (l1 > l2) {
                        long long temp = l1;
                        l1 = l2;
                        l2 = temp;
                    }
                    if (2 * clen <= (l2 - l1)) continue;
                    zoids.insert(trapezoid(c1, c2, m, n));
                }
            }
        }
        cout << "Case #" << i << ": " << zoids.size() << endl;
    }
    return 0;
}

最终也只是过了B-small-practice.in,尴尬=——=。

由于时间所迫,最后一道题就没有给出解答了~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值