zoukankan      html  css  js  c++  java
  • 与Wii控制手柄通信的托管代码库(转)

    2009-01-16 翻译

    HID Human Input Device     人工输入设备

    Wii Fit Balance Board       平衡板

    IR                          红外传感器

    Windows Driver Kit          Windows驱动开发包

    Wiimote                     Wii控制手柄

    Report                      报文

     

    2007-3-14 在Coding4Fun发布

     

    在本文中, Brian Peek演示了如何使用C#和VB.NET连接和使用任天堂控制手柄(Wiimote)。最终成果是一个可以在托管代码应用中方便使用的Wiimot托管代码API。

    Brian Peek

    ASPSOFT, Inc.

     

    难度: 中级

    时间: 1-3 小时

    花费: 小于$50 (如果已经有Wiimote则免费)

    软件: Visual C# or Visual Basic 2008 Express Editions

    硬件: Nintendo Wii控制手柄(Wiimote), 一个兼容的PC蓝牙适配器

    下载: CodePlex

     

    第0章       更新历史

    (最新版本已经为1.6, 以下为1.6的更新记录。译者注)

    •  
      • 新增对平衡板“重力中心”的计算(感谢Steven Battersby)
      • 结构声明为[Serializable] (Caio建议)
      • 电池(Battery)属性现在改为浮点型,能够反映电池剩余电量的百分比。
      • BatteryRaw为字节类型,原本用于保存电池属性。
      • WiimoteTest应用程序现在在启动时能够正确的读取扩展信息。
      • 在Wiimote对象中新增HIIDevicePath属性,以显示HID设备路径。
      • 写的时间延时由100毫秒改为50毫秒,这能够提高LED和滚轮的响应能力。

    6/15/08Version 1.5.2,平衡板能够真正的工作了……

    6/12/08 – 将Version 1.5.1 从CodePlex删除...事实证明该版本对大多数用户存在太多的BUG(尽管对我而言一直不错),期待1.5.2的很快到来...

    6/11/08Version 1.5.1在 CodePlex上发布。修改了一个关于平衡板的BUG。更正:  似乎很多人在使用该版本时发现仍然存在问题,原因是平衡板对响应时间的要求比较精确。等待1.5.2的到来

    6/9/08 – Version 1.5 在 CodePlex上发布。支持 Wii Fit Balance Board(平衡板)。

    6/3/08 –Version 1.4 发布并提供下载。最重要的改进是支持多个Wiimote。

    5/27/08 - Version 1.3发布并提供下载。改动量很大,一定要阅读内置的文档!

    1/29/08 - Version 1.2.1.0发布并提供下载。唯一的改进是对IR3和IR4的支持,因为我遇到了很多关于它们的问题。

    10/22/07 - Version 1.2.0.0发布并提供下载。修改了一些BUG并增加了新功能。源代码和二进制发布代码都在CodePlex上提供。同时提供了一个关于API使用的chm帮助文件。请留意库中包含的一个license文件详细描述的本软件的使用许可情况。使用许可对99%的用户没有任何变化,但由于我收到了大量的询问有关使用许可的电子邮件,因此附加了这个正式的说明。在正式发布中的docs目录下,readme.txt 和 license.txt有详细描述。

    6/12/07 - Version 1.1.0.0发布并提供下载。 修改了几个错误,新增了一个可选的写方法,这可能对使用蓝牙协议栈/适配器有麻烦的用户有所帮助。增加了对Vista/XP x64 的支持, 以及一个Microsoft Robotics Studio服务版本。更多信息见包含的 readme.txt的信息。另外,在我的新文档中描述了如何使用MSRS服务创建一个Wiimote遥控车。

    3/17/07 - Version 1.0.1.0发布并提供下载。 修正了API中关于校准数据的BUG。感谢James Darpinian 的指正!

     

    第1章       简介

    任天堂的Wii 控制手柄 (被称为Wiimote)是Wii系统中一个神奇的小控制器。它通过蓝牙系统与Wii连接, 因此能够与其它任何支持蓝牙的设备连接。

    如果你仅仅关注于如何使用这个库文件,而不关心其实现细节,可以直接跳转到API应用章节(第7章)。

    在阅读代码之前,有两个网站应当仔细的浏览一下。99%的Wiimote发送和接收数据的辨识工作由这两个网站完成。在网站上有对协议很详细的描述,我在此就不再重复。没有在这些网站上发贴的人们,本文中的功能根本不可能实现。

    第2章       开始连接

    这里可能是最关键的时刻,Wiimote不是能和地球上所有蓝牙设备和协议栈进行通信的, 如果下面几步不能通过,我也不能提供任何帮助。能够工作,或者不能,你只能进行祈祷了...

    1.启动你的蓝牙软件并开始搜索设备。

    2.按下Wiimote 的1 和 2 按键,会看到底部的LED开始闪烁。在完成之前不要松开按键。对于平衡板,打开下部的电池盖,按下一个红色的小的同步按钮。

    3.Wiimotes应当在设备列表中显示为Nintendo RVL-CNT-01。 平衡板显示为 Nintendo RVL-WBC-01。如果没找到,重新启动并再试一下。

    4.在向导中点击下一步。如果系统提示输入安全码或者PIN,不要输入或者选择跳过。

    5.如果被要求选择Wiimote 使用何种服务,选择键盘/鼠标/HID服务其中的一种。

    6.完成向导窗口。

    这就可以了。底部的LED继续闪烁,而且设备显示在蓝牙设备列表中。运行源代码中的应用程序,你应该可以看到显示的数字变化,这标志着成功的连接了Wiimote设备。如果没有变化或者出现错误, 只能重新试一下。要是还不行, 很不幸,你可能使用的是一种不兼容的设备或协议栈。(这个过程确实需要多次尝试,可以参考附录中译者的连接方法作为参考。)

    第3章       进入令人激动的HID和P/Invoke世界

    当Wiimote可以与你的PC匹配,它被视为一种HID兼容设备。因此,为了连接该设备,我们必须使用HID和设备管理WIN32 API。不幸的是,目前的.NET运行环境中没有内置支持这些API,因此需要进入P/Invoke的领域。这些API在Windows驱动开发包(WDK)中定义, 如果希望看到原始的C头文件或阅读API文档,需要下载并安装最新的WDK

    P/Invoke, 或许你已经有所了解,允许用户在.NET中直接调用Win32 API。这里的难点是找到合法的方法名称和格式定义,能够正确的将串行化数据并传递到Win32。对此P/Invoke wiki是一个很好的资源,几乎所有本项目用到的方法都在此能找到。在本项目中,所有的P/Invoke方法在HIDImports类中定义。

    与Wiimote通信的过程如下:

    1.从Windows得到GUID和HID类定义。

    2.得到HID类中所有设备的操作句柄。

    3.在设备列表中进行遍历以得到每个设备的详细信息。

    4.比较制造商ID和产品ID是否为已知的Wiimote制造商ID(VID)和产品ID(PID)。

    5.在找到设备后,创建FileStream对设备进行读写操作。

    6.清除设备列表。

     该步骤的实现代码如下(有省略)

    VB

    ' read/write handle to the device
    Private mHandle As SafeFileHandle
    
    ' a pretty .NET stream to read/write from/to
    Private mStream As FileStream
    Private found As Boolean = False
    Private guid As Guid
    Private index As UInteger = 0
    
    ' 1. get the GUID of the HID class
    HIDImports.HidD_GetHidGuid(guid)
    
    ' 2. get a handle to all devices that are part of the HID class
    Dim hDevInfo As IntPtr = HIDImports.SetupDiGetClassDevs(guid, Nothing, IntPtr.Zero, HIDImports.DIGCF_DEVICEINTERFACE) ' | HIDImports.DIGCF_PRESENT);
    
    ' create a new interface data struct and initialize its size
    Dim diData As HIDImports.SP_DEVICE_INTERFACE_DATA = New HIDImports.SP_DEVICE_INTERFACE_DATA()
    diData.cbSize = Marshal.SizeOf(diData)
    
    ' 3. get a device interface to a single device (enumerate all devices)
    Do While HIDImports.SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, guid, index, diData)
        ' create a detail struct and set its size
        Dim diDetail As HIDImports.SP_DEVICE_INTERFACE_DETAIL_DATA = New HIDImports.SP_DEVICE_INTERFACE_DETAIL_DATA()
        diDetail.cbSize = 5 'should be: (uint)Marshal.SizeOf(diDetail);, but that's the wrong size
    
        Dim size As UInt32 = 0
    
        ' get the buffer size for this device detail instance (returned in the size parameter)
        HIDImports.SetupDiGetDeviceInterfaceDetail(hDevInfo, diData, IntPtr.Zero, 0, size, IntPtr.Zero)
    
        ' actually get the detail struct
        If HIDImports.SetupDiGetDeviceInterfaceDetail(hDevInfo, diData, diDetail, size, size, IntPtr.Zero) Then
            ' open a read/write handle to our device using the DevicePath returned
            mHandle = HIDImports.CreateFile(diDetail.DevicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, HIDImports.EFileAttributes.Overlapped, IntPtr.Zero)
    
            ' 4. create an attributes struct and initialize the size
            Dim attrib As HIDImports.HIDD_ATTRIBUTES = New HIDImports.HIDD_ATTRIBUTES()
            attrib.Size = Marshal.SizeOf(attrib)
    
            ' get the attributes of the current device
            If HIDImports.HidD_GetAttributes(mHandle.DangerousGetHandle(), attrib) Then
                ' if the vendor and product IDs match up
                If attrib.VendorID = VID AndAlso attrib.ProductID = PID Then
                    ' 5. create a nice .NET FileStream wrapping the handle above
                    mStream = New FileStream(mHandle, FileAccess.ReadWrite, REPORT_LENGTH, True)
                Else
                    mHandle.Close()
                End If
            End If
        End If
    
        ' move to the next device
        index += 1
    Loop
    
    ' 6. clean up our list
    HIDImports.SetupDiDestroyDeviceInfoList(hDevInfo)

    C#

    // read/write handle to the device
    private SafeFileHandle mHandle;
    
    // a pretty .NET stream to read/write from/to
    private FileStream mStream;
    bool found = false;
    Guid guid;
    uint index = 0;
    
    // 1. get the GUID of the HID class
    HIDImports.HidD_GetHidGuid(out guid);
    
    // 2. get a handle to all devices that are part of the HID class
    IntPtr hDevInfo = HIDImports.SetupDiGetClassDevs(ref guid, null, IntPtr.Zero, HIDImports.DIGCF_DEVICEINTERFACE);// | HIDImports.DIGCF_PRESENT);
    
    // create a new interface data struct and initialize its size
    HIDImports.SP_DEVICE_INTERFACE_DATA diData = new HIDImports.SP_DEVICE_INTERFACE_DATA();
    diData.cbSize = Marshal.SizeOf(diData);
    
    // 3. get a device interface to a single device (enumerate all devices)
    while(HIDImports.SetupDiEnumDeviceInterfaces(hDevInfo, IntPtr.Zero, ref guid, index, ref diData))
    {
        // create a detail struct and set its size
        HIDImports.SP_DEVICE_INTERFACE_DETAIL_DATA diDetail = new HIDImports.SP_DEVICE_INTERFACE_DETAIL_DATA();
        diDetail.cbSize = 5; //should be: (uint)Marshal.SizeOf(diDetail);, but that's the wrong size
    
        UInt32 size = 0;
    
        // get the buffer size for this device detail instance (returned in the size parameter)
        HIDImports.SetupDiGetDeviceInterfaceDetail(hDevInfo, ref diData, IntPtr.Zero, 0, out size, IntPtr.Zero);
    
        // actually get the detail struct
        if(HIDImports.SetupDiGetDeviceInterfaceDetail(hDevInfo, ref diData, ref diDetail, size, out size, IntPtr.Zero))
        {
            // open a read/write handle to our device using the DevicePath returned
            mHandle = HIDImports.CreateFile(diDetail.DevicePath, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, HIDImports.EFileAttributes.Overlapped, IntPtr.Zero);
    
            // 4. create an attributes struct and initialize the size
            HIDImports.HIDD_ATTRIBUTES attrib = new HIDImports.HIDD_ATTRIBUTES();
            attrib.Size = Marshal.SizeOf(attrib);
    
            // get the attributes of the current device
            if(HIDImports.HidD_GetAttributes(mHandle.DangerousGetHandle(), ref attrib))
            {
                // if the vendor and product IDs match up
                if(attrib.VendorID == VID && attrib.ProductID == PID)
                {
                    // 5. create a nice .NET FileStream wrapping the handle above
                    mStream = new FileStream(mHandle, FileAccess.ReadWrite, REPORT_LENGTH, true);
                }
                else
                    mHandle.Close();
            }
        }
    
        // move to the next device
        index++;
    }
    
    // 6. clean up our list
    HIDImports.SetupDiDestroyDeviceInfoList(hDevInfo);

    第4章       CreateFile 和 SafeFileHandles

    在看过上面的代码后,你可能注意到Wiimote的操作句柄是通过Win32的CreateFile方法打开的,而没有直接使用FileStream对象或者其它托管方式。这是由句柄创建方式的需要所决定的。diDetail结构中的 DevicePath 成员保存了一个非文件系统路径,Win32可以使用该句柄打开设备,而.NET仅允许文件系统路径,因此我们必须使用Win32方法。

    同时你可能注意到我们使用了SafeFileHandle 对象包装CreateFile调用返回的句柄。SafeFileHandle对象包装了本地(非托管的)的Win32句柄,允许安全的管理本地类型,并在应用退出时干净的关闭这些句柄。当然可以使用更容易的IntPtr,但我发现对本地类型这种方式是更为干净的处理方式。

    第5章       Wiimote I/O 和 HID 报文

    在HID世界中,数据以报文的方式发送和接收。简要的说,报文就是一个已定义长度的数据缓冲区,它带有的头信息决定了报文的内容。Wiimote接收和发送多种报文,都是22字节长,在上面提到的网站中有详细的描述。考虑到其数量和复杂性,如果你希望了解Wiimote的报文和数据内容,我建议你自行阅读wikis中的相关资料。

    现在我们得到了与Wiimote通信的 FileStream对象。因为报文会在同一时刻进行收发,所以必须采用异步I/O操作。在.NET中做到这点并不困难。在方法开始时进行一个异步读操作,并在缓冲区满后提供一个回调函数。在回调函数中,进行数据处理并重复调用该方法。

    VB 

    ' sure, we could find this out the hard way using HID, but trust me, it's 22
    Private Const REPORT_LENGTH As Integer = 22
    
    ' report buffer
    Private mBuff As Byte() = New Byte(REPORT_LENGTH - 1){}
    
    Private Sub BeginAsyncRead()
        ' if the stream is valid and ready
        If mStream.CanRead Then
            ' create a read buffer of the report size
            Dim buff As Byte() = New Byte(REPORT_LENGTH - 1){}
    
            ' setup the read and the callback
            mStream.BeginRead(buff, 0, REPORT_LENGTH, New AsyncCallback(AddressOf OnReadData), buff)
        End If
    End Sub
    
    Private Sub OnReadData(ByVal ar As IAsyncResult)
        ' grab the byte buffer
        Dim buff As Byte() = CType(ar.AsyncState, Byte())
    
        ' end the current read
        mStream.EndRead(ar)
    
        ' start reading again
        BeginAsyncRead()
    
        ' handle data....
    End Sub

    C#

    // sure, we could find this out the hard way using HID, but trust me, it's 22
    private const int REPORT_LENGTH = 22;
    
    // report buffer
    private byte[] mBuff = new byte[REPORT_LENGTH];
    
    private void BeginAsyncRead()
    {
        // if the stream is valid and ready
        if(mStream.CanRead)
        {
            // create a read buffer of the report size
            byte[] buff = new byte[REPORT_LENGTH];
    
            // setup the read and the callback
            mStream.BeginRead(buff, 0, REPORT_LENGTH, new AsyncCallback(OnReadData), buff);
        }
    }
    
    private void OnReadData(IAsyncResult ar)
    {
        // grab the byte buffer
        byte[] buff = (byte[])ar.AsyncState;
    
        // end the current read
        mStream.EndRead(ar);
    
        // start reading again
        BeginAsyncRead();
        
        // handle data....
    }

    第6章       完成!

    你可能不相信,但这些代码已经能够连接并与Wiimote通信。 接下来的代码包括解析接收的数据并向Wiimote发送格式正确的代码。正象上面提到的, 我没打算在此详细说明这些细节,网站上可以提供更好的说明。

    向Wiimote发送命令的方式如下:

    VB

    mStream.Write(mBuff, 0, REPORT_LENGTH)

    C#

    mStream.Write(mBuff, 0, REPORT_LENGTH);

    读取功能在上面的异步代码中完成。在收到22字节数据后, 调用OnReadData方法,然后正确的解析和使用这些数据。

     

    第7章       使用API

    如果不关心这些实现细节,可以直接跳转到本章,学习如何在你自己的应用程序中使用这些API。最简单的方法是通过源代码中带有的WiimoteTest 应用程序来了解如何实现这些工作。

     

    首先在源代码中添加WiimoteLib.dll 引用。然后,在应用程序中使用using/Imports 声明相关的命名空间。之后,就可以创建和使用一个Wiimote类。简单的初始化一个新的Wiimote类实例, 设置相应的事件处理以及希望返回数据的报文类型,并调用Connect方法。

    VB

    Imports WiimoteLib
    
    Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs)
        ' create a new instance of the Wiimote
        Dim wm As Wiimote = New Wiimote()
    
        ' setup the event to handle state changes
        AddHandler wm.WiimoteChanged, AddressOf wm_WiimoteChanged
    
        ' setup the event to handle insertion/removal of extensions
        AddHandler wm.WiimoteExtensionChanged, AddressOf wm_WiimoteExtensionChanged
    
        ' connect to the Wiimote
        wm.Connect()
    
        ' set the report type to return the IR sensor and accelerometer data (buttons always come back)
        wm.SetReportType(Wiimote.InputReport.IRAccel, True)
    End Sub
    
    
    Private Sub wm_WiimoteExtensionChanged(ByVal sender As Object, ByVal args As WiimoteExtensionChangedEventArgs)
        If args.Inserted Then
            wm.SetReportType(Wiimote.InputReport.IRExtensionAccel, True) ' return extension data
        Else
            wm.SetReportType(Wiimote.InputReport.IRAccel, True) ' back to original mode
        End If
    End Sub
    
    Private Sub wm_OnWiimoteChanged(ByVal sender As Object, ByVal args As WiimoteChangedEventArgs)
        ' current state information
        Dim ws As WiimoteState = args.WiimoteState
    
        ' write out the state of the A button    
        Debug.WriteLine(ws.ButtonState.A)
    End Sub

    C#

    using WiimoteLib;
    
    private void Form1_Load(object sender, EventArgs e)
    {
        // create a new instance of the Wiimote
        Wiimote wm = new Wiimote();
        
        // setup the event to handle state changes
        wm.WiimoteChanged += wm_WiimoteChanged;
        
        // setup the event to handle insertion/removal of extensions
        wm.WiimoteExtensionChanged += wm_WiimoteExtensionChanged;
        
        // connect to the Wiimote
        wm.Connect();
        
        // set the report type to return the IR sensor and accelerometer data (buttons always come back)
        wm.SetReportType(Wiimote.InputReport.IRAccel, true);
    }
    
    
    void wm_WiimoteExtensionChanged(object sender, WiimoteExtensionChangedEventArgs args)
    {
        if(args.Inserted)
            wm.SetReportType(Wiimote.InputReport.IRExtensionAccel, true);    // return extension data
        else
            wm.SetReportType(Wiimote.InputReport.IRAccel, true);            // back to original mode
    }
    
    void wm_WiimoteChanged(object sender, WiimoteChangedEventArgs args)
    {
        // current state information
        WiimoteState ws = args.WiimoteState;
    
        // write out the state of the A button    
        Debug.WriteLine(ws.ButtonState.A);
    }

    如果使用多个Wiimote,创建一个WiimoteCollection 对象,调用FindAllWiimotes方法进行初始化,对集合中的每个Wiimote对象作为一个单独的实例进行使用。

    VB

    Dim wc As New WiimoteCollection()
    wc.FindAllWiimotes()
    
    For Each wm As Wiimote In wc
        AddHandler wm.WiimoteChanged, AddressOf wm_WiimoteChanged
        AddHandler wm.WiimoteExtensionChanged, AddressOf wm_WiimoteExtensionChanged
        
        wm.Connect()
        wm.SetReportType(InputReport.IRAccel, True)
    Next wm

    C#

    WiimoteCollection wc = new WiimoteCollection();
    wc.FindAllWiimotes();
    
    foreach(Wiimote wm in wc)
    {
        wm.WiimoteChanged += wm_WiimoteChanged;
        wm.WiimoteExtensionChanged += wm_WiimoteExtensionChanged;
        
        wm.Connect();
        wm.SetReportType(InputReport.IRAccel, true);
    }

    有两种方式从API中取得数据:事件和轮询。在事件模式中,上面所示,首先订阅WiimoteChanged 事件。然后,当数据从Wiimote发送到PC时,一个事件对象将发送到应用程序中的事件处理器中。如果不使用事件模型,可以简单的在任何时刻从Wiimote类的WiimoteState属性中取得状态信息。

    报文类型

    目前托管库仅支持Wiimote报文中有限的几种类型, 不过现在实现的报文类型已经能够返回所有必要的数据,以及目前的扩展信息了。这些报文类型包括:

    • Buttons – 仅按钮数据
    • ButtonsAccel – 按钮和加速度传感器数据
    • IRAccel – 按钮,加速度传感器和IR数据
    • ButtonsExtension – 按钮和扩展数据
    • ExtensionAccel -按钮,加速度传感器和扩展数据
    • IRExtensionAccel -按钮,加速度传感器,IR和扩展数据

    报文类型可以通过SetReportType 方法设置,使用不同的报文类型,决定是否不停的发送数据,或只在控制器状态的改变时得到报文。

    第8章       扩展

    目前支持三种Wii扩展:Nunchuk, Classic Controller和Guitar Hero controller。如果希望使用这些扩展, 需要设置WiimoteExtensionChanged的事件处理器。当该事件被调用,可以通过检查事件变量确定是否有扩展插入或移除,以及插入扩展的类型。在事件处理器中,需要使用SetReportType改为支持扩展数据的报文类型,否则扩展数据不会返回。

    如果使用一种严格的轮询操作,应当检查WiimoteState 属性中的ExtensionExtensionType参数,以确定扩展模块的插入和移除。

    平衡板被视为一种带有扩展功能的Wiimote控制器。报文类型为内部设置,对该设备的任何报文类型改变将被忽略。平衡板的开关按钮对应Wiimote的A按钮, LED对应Wiimote的LED1。其它Wiimote属性被忽略。其它平衡板的信息在WiimoteState 对象中的BalanceBoard 结构中。

    第9章       帮助信息

    整个库的核心在WiimoteState对象中。在下载包中的chm帮助文件中可以查找全部可用的属性。 

    第10章       将来的工作

    目前为止,还不能支持Wiimote中的扬声器。我有可能在将来的更新中增加这些功能。另外还有几种报文类型没有实现,不过现在实现的报文类型已经能够返回所有必要的信息了。我还希望能够添加一个“高层”的功能,例如返回倾斜/翻转角度,IR传感器的鼠标位置等。留意本文和我的博客中关于本库的更新信息。

    第11章       结论

    到此,我们已经达到了目的。一个全功能的Wiimote托管代码库。尝试一下,将它集成到你现有的应用程序中,或者做些全新的东西!我期待你能够利用它进行创造性的应用...

    如果对库的使用有任何问题,或者有其它的功能需求(声音除外),可以直接联系我或在这个项目的专有论坛上发贴。

    第12章       链接

    第13章       作者介绍

    Brian是一名 Microsoft C# MVP,自从2000年.NET发布早期测试版以来一直致力于.NET的开发,他在利用微软的技术和平台提供解决方案方面有更长的历史。除了.NET,Brian在各种CPU上的C, C++和汇编语言方面也有深入的研究。他精通很多种技术, 包括WEB开发,文档图像,GIS, 图形,游戏开发和硬件接口。 Brian在卫生保健行业的应用开发有很深厚的背景,提供多种便携设备解决方案,包括平板电脑和PDA。此外,他还是New Riders出版社出版的"深入 ASP.NET"的合作者,目前正在合著一本名为"程序员,爱好者和游戏开发人员的10个.NET Coding4Fun项目 "的书籍,该书将于2008年由 O'Reilly出版社出版。 Brian同时为 MSDN的 Coding4Fun 网站按月提交文章。

    http://blogs.msdn.com/coding4fun/archive/2007/03/14/1879033.aspx

  • 相关阅读:
    BNUOJ 12756 Social Holidaying(二分匹配)
    HDU 1114 Piggy-Bank(完全背包)
    HDU 2844 Coins (多重背包)
    HDU 2602 Bone Collector(01背包)
    HDU 1171 Big Event in HDU(01背包)
    HDU 2571 命运 (入门dp)
    HDU 1069 Monkey and Banana(最长递减子序列)
    HDU 1160 FatMouse's Speed (最长上升子序列)
    HDU 2594 KMP
    POJ 3783 Balls --扔鸡蛋问题 经典DP
  • 原文地址:https://www.cnblogs.com/vonly/p/4212135.html
Copyright © 2011-2022 走看看